greetd: re-add embedded greeter for archinstall compatibility

This commit is contained in:
bbedward
2026-08-29 12:28:05 -04:00
parent 104bbebbc2
commit d272e7d9da
30 changed files with 3953 additions and 47 deletions
+38
View File
@@ -497,3 +497,41 @@ jobs:
exit 0
fi
bash scripts/notify-issues.sh "$PREVIOUS_TAG" "$TAG"
# Pin the satellite repos to this release's version. Each dispatch target
# decides for itself whether to tag: X.Y.0 always tags, X.Y.Z skips when the
# repo has no commits of its own since its last tag.
fanout:
name: Pin ${{ matrix.repo }}
runs-on: ubuntu-latest
needs: [release]
if: ${{ !contains(inputs.tag, '-') }}
strategy:
fail-fast: false
matrix:
repo:
- dgop
- dankgo
- dank-qml-common
- dankcalendar
- danksearch
- dank-greeter
steps:
- name: Create GitHub App token
id: app_token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ matrix.repo }}
- name: Dispatch pin-release
env:
GH_TOKEN: ${{ steps.app_token.outputs.token }}
REPO: ${{ github.repository_owner }}/${{ matrix.repo }}
TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
gh workflow run pin-release.yml --repo "$REPO" -f version="$TAG"
echo "dispatched pin-release ${TAG} to ${REPO}" >> "$GITHUB_STEP_SUMMARY"
+1 -1
View File
@@ -62,7 +62,7 @@ func greeterPackageInstallHint() string {
case distros.FamilyFedora:
return "Install with 'sudo dnf install dms-greeter' (requires COPR: sudo dnf copr enable avengemedia/danklinux)"
case distros.FamilyArch:
return "Install from AUR with 'paru -S greetd-dms-greeter-git' or 'yay -S greetd-dms-greeter-git'"
return "Install from AUR with 'paru -S greetd-dms-greeter-bin' or 'yay -S greetd-dms-greeter-bin'"
case distros.FamilyVoid:
return "Install with 'sudo xbps-install -S dms-greeter' (requires DMS XBPS repo: echo 'repository=https://void.danklinux.com/dms/current' | sudo tee /etc/xbps.d/dms.conf)"
default:
+11 -3
View File
@@ -13,6 +13,8 @@ Singleton {
readonly property int cacheConfigVersion: 3
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericCacheLocation)
readonly property string _stateDir: Paths.strip(_stateUrl)
@@ -88,6 +90,8 @@ Singleton {
})
Component.onCompleted: {
if (isGreeterMode)
return;
loadCache();
}
@@ -281,7 +285,7 @@ Singleton {
FileView {
id: launcherCacheFile
path: _stateDir + "/DankMaterialShell/launcher_cache.json"
path: isGreeterMode ? "" : _stateDir + "/DankMaterialShell/launcher_cache.json"
blockLoading: true
blockWrites: true
atomicWrites: true
@@ -291,15 +295,19 @@ Singleton {
FileView {
id: cacheFile
path: _stateDir + "/DankMaterialShell/cache.json"
path: isGreeterMode ? "" : _stateDir + "/DankMaterialShell/cache.json"
blockLoading: true
blockWrites: true
atomicWrites: true
watchChanges: true
watchChanges: !isGreeterMode
onLoaded: {
if (isGreeterMode)
return;
parseCache(cacheFile.text());
}
onLoadFailed: error => {
if (isGreeterMode)
return;
log.info("No cache file found, starting fresh");
}
}
+47 -5
View File
@@ -16,6 +16,8 @@ Singleton {
readonly property int sessionConfigVersion: 4
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
signal loaded
signal brightnessDisplayHintChanged(string deviceName)
signal loadErrorOccurred(string file, string message)
@@ -257,6 +259,11 @@ Singleton {
_hasUnsavedChanges = false;
_pendingMigration = null;
if (isGreeterMode) {
parseSettings(greeterSessionFile.text());
return;
}
try {
const txt = settingsFile.text();
let obj = (txt && txt.trim()) ? JSON.parse(txt) : null;
@@ -294,7 +301,7 @@ Singleton {
_loadedSessionSnapshot = getCurrentSessionJson();
_hasLoaded = true;
if (typeof Theme !== "undefined")
if (!isGreeterMode && typeof Theme !== "undefined")
Theme.generateSystemThemesFromCurrentTheme();
loaded();
@@ -375,7 +382,7 @@ Singleton {
_loadedSessionSnapshot = getCurrentSessionJson();
_hasLoaded = true;
if (typeof Theme !== "undefined")
if (!isGreeterMode && typeof Theme !== "undefined")
Theme.generateSystemThemesFromCurrentTheme();
loaded();
@@ -398,7 +405,7 @@ Singleton {
}
function saveSettings() {
if (_parseError || !_hasLoaded)
if (isGreeterMode || _parseError || !_hasLoaded)
return;
settingsFile.setText(getCurrentSessionJson());
if (_isReadOnly)
@@ -1588,12 +1595,14 @@ Singleton {
FileView {
id: settingsFile
path: StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/DankMaterialShell/session.json"
path: isGreeterMode ? "" : StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/DankMaterialShell/session.json"
blockLoading: true
blockWrites: true
atomicWrites: true
watchChanges: true
watchChanges: !isGreeterMode
onLoaded: {
if (isGreeterMode)
return;
_hasUnsavedChanges = false;
parseSettings(settingsFile.text());
}
@@ -1603,6 +1612,39 @@ Singleton {
}
}
readonly property string _greeterCacheDir: Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter"
property string greeterSessionBaseDir: root._greeterCacheDir
function setGreeterSessionBaseDir(dir) {
const next = dir || root._greeterCacheDir;
if (greeterSessionBaseDir === next)
return;
greeterSessionBaseDir = next;
if (isGreeterMode)
greeterSessionFile.reload();
}
function resetGreeterSessionBaseDir() {
setGreeterSessionBaseDir(root._greeterCacheDir);
}
FileView {
id: greeterSessionFile
path: root.greeterSessionBaseDir ? (root.greeterSessionBaseDir + "/session.json") : ""
preload: isGreeterMode
blockLoading: false
blockWrites: true
watchChanges: false
printErrors: true
onLoaded: {
if (isGreeterMode) {
parseSettings(greeterSessionFile.text());
}
}
}
Process {
id: sessionWritableCheckProcess
+26 -4
View File
@@ -17,6 +17,8 @@ Singleton {
readonly property int settingsConfigVersion: 16
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
enum Position {
Top,
Bottom,
@@ -1357,10 +1359,14 @@ Singleton {
signal notificationPopupsInvalidated
function refreshAuthAvailability() {
if (isGreeterMode)
return;
Processes.detectAuthCapabilities();
}
Component.onCompleted: {
if (isGreeterMode)
return;
Processes.settingsRoot = root;
loadSettings();
initializeListModels();
@@ -1423,6 +1429,8 @@ Singleton {
}
function checkIconThemeDrift() {
if (isGreeterMode)
return;
if (resolveIconTheme() === "System Default")
return;
if (!SessionData.lastAppliedIconTheme)
@@ -1583,6 +1591,8 @@ Singleton {
}
function scheduleAuthApply() {
if (isGreeterMode)
return;
Qt.callLater(() => {
Processes.settingsRoot = root;
Processes.scheduleAuthApply();
@@ -1590,6 +1600,8 @@ Singleton {
}
function scheduleGreeterAutoLoginSync() {
if (isGreeterMode)
return;
Qt.callLater(() => {
Processes.settingsRoot = root;
Processes.scheduleGreeterAutoLoginSync();
@@ -1597,6 +1609,8 @@ Singleton {
}
function markGreeterSyncPending(who, key, oldValue) {
if (isGreeterMode)
return;
if (!(key in SessionData.greeterSyncBaseline)) {
var baseline = Object.assign({}, SessionData.greeterSyncBaseline);
baseline[key] = oldValue;
@@ -3343,11 +3357,11 @@ Singleton {
FileView {
id: settingsFile
path: StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json"
path: isGreeterMode ? "" : StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json"
blockLoading: true
blockWrites: true
atomicWrites: true
watchChanges: true
watchChanges: !isGreeterMode
onFileChanged: {
if (_selfWrite) {
_selfWrite = false;
@@ -3356,6 +3370,8 @@ Singleton {
settingsFileReloadDebounce.restart();
}
onLoaded: {
if (isGreeterMode)
return;
const wasLoaded = _hasLoaded;
const prevFrameEnabled = frameEnabled;
const prevFrameMode = frameMode;
@@ -3398,6 +3414,8 @@ Singleton {
updateFrameCompositorLayout();
}
onLoadFailed: error => {
if (isGreeterMode)
return;
applyStoredTheme();
}
onSaveFailed: error => {
@@ -3409,16 +3427,20 @@ Singleton {
FileView {
id: pluginSettingsFile
path: pluginSettingsPath
path: isGreeterMode ? "" : pluginSettingsPath
blockLoading: true
blockWrites: true
atomicWrites: true
printErrors: false
watchChanges: true
watchChanges: !isGreeterMode
onLoaded: {
if (isGreeterMode)
return;
parsePluginSettings(pluginSettingsFile.text());
}
onLoadFailed: error => {
if (isGreeterMode)
return;
const msg = String(error || "");
if (!_isMissingPluginSettingsError(error))
log.warn("Failed to load plugin_settings.json. Error:", msg);
+86 -18
View File
@@ -8,6 +8,7 @@ import Quickshell.Io
import qs.Common
import qs.DankCommon.Common as DankCommon
import qs.Services
import qs.Modules.Greetd
import "StockThemes.js" as StockThemes
Singleton {
@@ -145,11 +146,13 @@ Singleton {
Quickshell.execDetached(["mkdir", "-p", stateDir]);
// shellDir may be an embedded-UI extraction, which is read-only and
// unexecutable (dankgo shellapp/shellfs makeReadOnly chmods 0444)
Quickshell.execDetached(["bash", shellDir + "/scripts/gtk.sh", configDir, "assets", "", shellDir]);
if (typeof SessionData === "undefined" || !SessionData.isGreeterMode)
Quickshell.execDetached(["bash", shellDir + "/scripts/gtk.sh", configDir, "assets", "", shellDir]);
Proc.runCommand("matugenCheck", ["sh", "-c", "command -v matugen"], (output, code) => {
matugenAvailable = (code === 0) && !envDisableMatugen;
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
if (!matugenAvailable) {
if (!matugenAvailable || isGreeterMode) {
return;
}
@@ -1130,11 +1133,26 @@ Singleton {
return presetMap[SettingsData.modalAnimationSpeed] ?? 150;
}
property real cornerRadius: typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12
property real cornerRadius: {
if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") {
return GreetdSettings.cornerRadius;
}
return typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12;
}
property string fontFamily: typeof SettingsData !== "undefined" ? resolvedFontFamily(SettingsData.fontFamily) : DankCommon.Fonts.sans
property string fontFamily: {
if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") {
return resolvedFontFamily(GreetdSettings.getEffectiveFontFamily());
}
return typeof SettingsData !== "undefined" ? resolvedFontFamily(SettingsData.fontFamily) : DankCommon.Fonts.sans;
}
property string monoFontFamily: typeof SettingsData !== "undefined" ? resolvedMonoFontFamily(SettingsData.monoFontFamily) : DankCommon.Fonts.mono
property string monoFontFamily: {
if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") {
return resolvedMonoFontFamily(GreetdSettings.monoFontFamily);
}
return typeof SettingsData !== "undefined" ? resolvedMonoFontFamily(SettingsData.monoFontFamily) : DankCommon.Fonts.mono;
}
function resolvedFontFamily(family) {
if (family === defaultFontFamily)
@@ -1148,9 +1166,19 @@ Singleton {
return family;
}
property int fontWeight: typeof SettingsData !== "undefined" ? SettingsData.fontWeight : Font.Normal
property int fontWeight: {
if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") {
return GreetdSettings.fontWeight;
}
return typeof SettingsData !== "undefined" ? SettingsData.fontWeight : Font.Normal;
}
property real fontScale: typeof SettingsData !== "undefined" ? SettingsData.fontScale : 1.0
property real fontScale: {
if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") {
return GreetdSettings.fontScale;
}
return typeof SettingsData !== "undefined" ? SettingsData.fontScale : 1.0;
}
property real spacingXXS: 2
property real spacingXS: 4
@@ -1208,12 +1236,22 @@ Singleton {
currentThemeCategory = "generic";
}
}
if (savePrefs && typeof SettingsData !== "undefined") {
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
if (savePrefs && typeof SettingsData !== "undefined" && !isGreeterMode) {
SettingsData.set("currentThemeCategory", currentThemeCategory);
SettingsData.set("currentThemeName", currentTheme);
}
generateSystemThemesFromCurrentTheme();
if (!isGreeterMode) {
generateSystemThemesFromCurrentTheme();
}
}
function applyGreeterTheme(themeName) {
switchTheme(themeName, false, false);
if (themeName === dynamic && dynamicColorsFileView.path) {
dynamicColorsFileView.reload();
}
}
function setLightMode(light, savePrefs = true, enableTransition = false) {
@@ -1230,14 +1268,17 @@ Singleton {
return;
}
if (savePrefs && typeof SessionData !== "undefined") {
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode) {
SessionData.setLightMode(light);
}
if (typeof SettingsData !== "undefined") {
SettingsData.updateCosmicThemeMode(light);
if (!isGreeterMode) {
if (typeof SettingsData !== "undefined") {
SettingsData.updateCosmicThemeMode(light);
}
generateSystemThemesFromCurrentTheme();
}
generateSystemThemesFromCurrentTheme();
}
function toggleLightMode(savePrefs = true) {
@@ -1275,7 +1316,8 @@ Singleton {
if (themeData.variants.type === "multi" && themeData.variants.flavors && themeData.variants.accents) {
const defaults = themeData.variants.defaults || {};
const modeDefaults = defaults[colorMode] || defaults.dark || {};
const stored = typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, modeDefaults, colorMode) : modeDefaults;
const isGreeterMode = typeof SessionData !== "undefined" && SessionData.isGreeterMode;
const stored = isGreeterMode ? (GreetdSettings.registryThemeVariants[themeId]?.[colorMode] || modeDefaults) : (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, modeDefaults, colorMode) : modeDefaults);
var flavorId = stored.flavor || modeDefaults.flavor || "";
const accentId = stored.accent || modeDefaults.accent || "";
var flavor = findVariant(themeData.variants.flavors, flavorId);
@@ -1301,7 +1343,8 @@ Singleton {
}
if (themeData.variants.options && themeData.variants.options.length > 0) {
const selectedVariantId = typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeVariant(themeId, themeData.variants.default) : themeData.variants.default;
const isGreeterMode = typeof SessionData !== "undefined" && SessionData.isGreeterMode;
const selectedVariantId = isGreeterMode ? (typeof GreetdSettings.registryThemeVariants[themeId] === "string" ? GreetdSettings.registryThemeVariants[themeId] : themeData.variants.default) : (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeVariant(themeId, themeData.variants.default) : themeData.variants.default);
const variant = findVariant(themeData.variants.options, selectedVariantId);
if (variant) {
const variantColors = variant[colorMode] || variant.dark || variant.light || {};
@@ -1642,7 +1685,8 @@ Singleton {
}
function generateSystemThemesFromCurrentTheme() {
if (!matugenAvailable)
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
if (!matugenAvailable || isGreeterMode)
return;
_lastGenerateMs = Date.now();
@@ -2132,11 +2176,32 @@ Singleton {
}
}
readonly property string _greeterCacheDir: Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter"
property string greeterColorsBaseDir: root._greeterCacheDir
function setGreeterColorsBaseDir(dir) {
const next = dir || root._greeterCacheDir;
if (greeterColorsBaseDir === next)
return;
greeterColorsBaseDir = next;
if (typeof SessionData !== "undefined" && SessionData.isGreeterMode)
dynamicColorsFileView.reload();
}
function resetGreeterColorsBaseDir() {
setGreeterColorsBaseDir(root._greeterCacheDir);
}
FileView {
id: dynamicColorsFileView
path: stateDir + "/dms-colors.json"
path: {
if (SessionData.isGreeterMode)
return root.greeterColorsBaseDir ? (root.greeterColorsBaseDir + "/colors.json") : "";
return stateDir + "/dms-colors.json";
}
blockLoading: false
watchChanges: true
watchChanges: !SessionData.isGreeterMode
function parseAndLoadColors() {
try {
@@ -2178,6 +2243,9 @@ Singleton {
if (currentTheme !== dynamic)
return;
if (SessionData.isGreeterMode)
return;
if (workerRunning) {
colorsReloadRetry.restart();
return;
+6 -6
View File
@@ -15,14 +15,14 @@ Singleton {
property var settingsRoot: null
onSettingsRootChanged: {
if (settingsRoot)
if (settingsRoot && !settingsRoot.isGreeterMode)
consumeGreeterAutoLoginPendingSync();
}
readonly property string greeterAutoLoginPendingSyncPath: (Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter") + "/.local/state/auto-login-sync-pending"
function consumeGreeterAutoLoginPendingSync() {
if (!settingsRoot)
if (!settingsRoot || settingsRoot.isGreeterMode)
return;
greeterAutoLoginPendingCheckProcess.running = true;
}
@@ -289,7 +289,7 @@ Singleton {
property string authApplyTerminalFallbackStderr: ""
function scheduleAuthApply() {
if (!settingsRoot)
if (!settingsRoot || settingsRoot.isGreeterMode)
return;
authApplyQueued = true;
@@ -302,7 +302,7 @@ Singleton {
}
function beginAuthApply() {
if (!authApplyQueued || authApplyRunning || !settingsRoot)
if (!authApplyQueued || authApplyRunning || !settingsRoot || settingsRoot.isGreeterMode)
return;
authApplyQueued = false;
@@ -341,7 +341,7 @@ Singleton {
property string greeterAutoLoginSyncStderr: ""
function scheduleGreeterAutoLoginSync() {
if (!settingsRoot)
if (!settingsRoot || settingsRoot.isGreeterMode)
return;
greeterAutoLoginSyncQueued = true;
@@ -354,7 +354,7 @@ Singleton {
}
function beginGreeterAutoLoginSync() {
if (!greeterAutoLoginSyncQueued || greeterAutoLoginSyncRunning || !settingsRoot)
if (!greeterAutoLoginSyncQueued || greeterAutoLoginSyncRunning || !settingsRoot || settingsRoot.isGreeterMode)
return;
greeterAutoLoginSyncQueued = false;
+9
View File
@@ -0,0 +1,9 @@
import QtQuick
import Quickshell
import qs.Modules.Greetd
Scope {
id: root
GreeterSurface {}
}
+20
View File
@@ -0,0 +1,20 @@
.pragma library
function readBoolOverride(envReader, names, fallbackValue) {
for (let i = 0; i < names.length; i++) {
const name = names[i];
const raw = envReader(name);
if (raw === undefined || raw === null || raw === "")
continue;
const normalized = String(raw).trim().toLowerCase();
if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on")
return true;
if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off")
return false;
console.warn("Invalid boolean override for", name + ":", raw, "- trying next override/fallback");
}
return fallbackValue;
}
+162
View File
@@ -0,0 +1,162 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import "GreetdEnv.js" as GreetdEnv
import qs.Services
Singleton {
id: root
readonly property var log: Log.scoped("GreetdMemory")
readonly property string greetCfgDir: Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter"
readonly property string sessionConfigPath: greetCfgDir + "/session.json"
readonly property string memoryFile: greetCfgDir + "/.local/state/memory.json"
readonly property bool rememberLastSession: GreetdEnv.readBoolOverride(Quickshell.env, ["DMS_GREET_REMEMBER_LAST_SESSION", "DMS_SAVE_SESSION"], true)
readonly property bool rememberLastUser: GreetdEnv.readBoolOverride(Quickshell.env, ["DMS_GREET_REMEMBER_LAST_USER", "DMS_SAVE_USERNAME"], true)
property string lastSessionId: ""
property string lastSessionDesktopId: ""
property string lastSessionExec: ""
property string lastSuccessfulUser: ""
property bool memoryReady: false
property bool isLightMode: false
property bool nightModeEnabled: false
Component.onCompleted: {
loadMemory();
loadSessionConfig();
}
function loadMemory() {
parseMemory(memoryFileView.text());
}
function loadSessionConfig() {
parseSessionConfig(sessionConfigFileView.text());
}
function parseSessionConfig(content) {
try {
if (content && content.trim()) {
const config = JSON.parse(content);
isLightMode = config.isLightMode !== undefined ? config.isLightMode : false;
nightModeEnabled = config.nightModeEnabled !== undefined ? config.nightModeEnabled : false;
}
} catch (e) {
log.warn("Failed to parse greeter session config:", e);
}
}
function parseMemory(content) {
try {
if (!content || !content.trim())
return;
const memory = JSON.parse(content);
lastSessionId = rememberLastSession ? (memory.lastSessionId || "") : "";
lastSessionDesktopId = rememberLastSession ? (memory.lastSessionDesktopId || "") : "";
lastSessionExec = rememberLastSession ? (memory.lastSessionExec || "") : "";
lastSuccessfulUser = rememberLastUser ? (memory.lastSuccessfulUser || "") : "";
if (!rememberLastSession || !rememberLastUser)
saveMemory();
} catch (e) {
log.warn("Failed to parse greetd memory:", e);
}
}
function saveMemory() {
let memory = {};
if (rememberLastSession && lastSessionId)
memory.lastSessionId = lastSessionId;
if (rememberLastSession && lastSessionDesktopId)
memory.lastSessionDesktopId = lastSessionDesktopId;
if (rememberLastUser && lastSuccessfulUser)
memory.lastSuccessfulUser = lastSuccessfulUser;
memoryFileView.setText(JSON.stringify(memory, null, 2));
}
function setLastSession(id, desktopId) {
if (!rememberLastSession) {
if (lastSessionId !== "" || lastSessionDesktopId !== "" || lastSessionExec !== "") {
lastSessionId = "";
lastSessionDesktopId = "";
lastSessionExec = "";
saveMemory();
}
return;
}
lastSessionId = id || "";
lastSessionDesktopId = desktopId || "";
lastSessionExec = "";
if (!lastSessionId)
lastSessionDesktopId = "";
saveMemory();
}
function setLastSessionId(id) {
setLastSession(id, lastSessionDesktopId);
}
function setLastSessionDesktopId(id) {
setLastSession(lastSessionId, id);
}
function setLastSessionExec(exec) {
if (!rememberLastSession) {
if (lastSessionExec !== "") {
lastSessionExec = "";
saveMemory();
}
return;
}
if (lastSessionExec !== "") {
lastSessionExec = "";
saveMemory();
}
}
function setLastSuccessfulUser(username) {
if (!rememberLastUser) {
if (lastSuccessfulUser !== "") {
lastSuccessfulUser = "";
saveMemory();
}
return;
}
lastSuccessfulUser = username || "";
saveMemory();
}
FileView {
id: memoryFileView
path: root.memoryFile
blockLoading: false
blockWrites: false
atomicWrites: true
watchChanges: false
printErrors: false
onLoaded: {
parseMemory(memoryFileView.text());
root.memoryReady = true;
}
onLoadFailed: {
root.memoryReady = true;
}
}
FileView {
id: sessionConfigFileView
path: root.sessionConfigPath
blockLoading: false
blockWrites: true
atomicWrites: false
watchChanges: false
printErrors: false
onLoaded: {
parseSessionConfig(sessionConfigFileView.text());
}
onLoadFailed: {}
}
}
@@ -0,0 +1,230 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Services
import "GreetdEnv.js" as GreetdEnv
Singleton {
id: root
readonly property var log: Log.scoped("GreetdSettings")
readonly property string _greeterCacheDir: Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter"
property string configBaseDir: root._greeterCacheDir
readonly property string configPath: root.configBaseDir ? (root.configBaseDir + "/settings.json") : ""
readonly property string greeterWallpaperOverridePath: root.configBaseDir ? (root.configBaseDir + "/greeter_wallpaper_override.jpg") : ""
function setConfigBaseDir(dir) {
const next = dir || root._greeterCacheDir;
if (configBaseDir === next)
return;
configBaseDir = next;
settingsLoaded = false;
settingsFile.reload();
}
function resetConfigBaseDir() {
setConfigBaseDir(root._greeterCacheDir);
}
property string currentThemeName: "purple"
property bool settingsLoaded: false
property string customThemeFile: ""
property var registryThemeVariants: ({})
property string matugenScheme: "scheme-tonal-spot"
property string clockFormat: "auto"
readonly property bool localeUses24Hour: {
const fmt = Qt.locale().timeFormat(Locale.ShortFormat).replace(/'[^']*'/g, "");
return !/[aA]/.test(fmt);
}
readonly property bool use24HourClock: clockFormat === "24h" ? true : (clockFormat === "12h" ? false : localeUses24Hour)
property bool showSeconds: false
property bool padHours12Hour: false
property string greeterLockDateFormat: ""
property string greeterFontFamily: ""
property string greeterWallpaperFillMode: ""
property bool useFahrenheit: false
property bool nightModeEnabled: false
property string weatherLocation: "New York, NY"
property string weatherCoordinates: "40.7128,-74.0060"
property bool useAutoLocation: false
property bool weatherEnabled: true
property string iconTheme: "System Default"
property bool useOSLogo: false
property string osLogoColorOverride: ""
property real osLogoBrightness: 0.5
property real osLogoContrast: 1
property string fontFamily: "Inter Variable"
property string monoFontFamily: "Fira Code"
property int fontWeight: Font.Normal
property real fontScale: 1.0
property real cornerRadius: 12
property string widgetBackgroundColor: "sch"
property string lockDateFormat: ""
property bool lockScreenShowPowerActions: true
property bool lockScreenShowProfileImage: true
property bool rememberLastSession: true
property bool rememberLastUser: true
property bool greeterAutoLogin: false
property bool greeterEnableFprint: false
property bool greeterEnableU2f: false
property string greeterWallpaperPath: ""
property bool powerActionConfirm: true
property real powerActionHoldDuration: 0.5
property var powerMenuActions: ["reboot", "logout", "poweroff", "lock", "suspend", "restart"]
property string powerMenuDefaultAction: "logout"
property bool powerMenuGridLayout: false
property var screenPreferences: ({})
property int animationSpeed: 2
property string wallpaperFillMode: "Fill"
property string wallpaperBackgroundColorMode: "black"
property string wallpaperBackgroundCustomColor: "#000000"
readonly property color effectiveWallpaperBackgroundColor: {
switch (wallpaperBackgroundColorMode) {
case "black":
return "#000000";
case "white":
return "#ffffff";
case "primary":
return (typeof Theme !== "undefined") ? Theme.primary : "#000000";
case "surface":
return (typeof Theme !== "undefined") ? Theme.surfaceContainer : "#000000";
case "custom":
return wallpaperBackgroundCustomColor;
default:
return "#000000";
}
}
function parseSettings(content) {
try {
let settings = {};
if (content && content.trim()) {
settings = JSON.parse(content);
}
const envRememberLastSession = GreetdEnv.readBoolOverride(Quickshell.env, ["DMS_GREET_REMEMBER_LAST_SESSION", "DMS_SAVE_SESSION"], undefined);
const envRememberLastUser = GreetdEnv.readBoolOverride(Quickshell.env, ["DMS_GREET_REMEMBER_LAST_USER", "DMS_SAVE_USERNAME"], undefined);
currentThemeName = settings.currentThemeName !== undefined ? settings.currentThemeName : "purple";
customThemeFile = settings.customThemeFile !== undefined ? settings.customThemeFile : "";
registryThemeVariants = settings.registryThemeVariants !== undefined ? settings.registryThemeVariants : ({});
matugenScheme = settings.matugenScheme !== undefined ? settings.matugenScheme : "scheme-tonal-spot";
clockFormat = settings.clockFormat !== undefined ? settings.clockFormat : (settings.use24HourClock !== undefined ? (settings.use24HourClock ? "24h" : "12h") : "auto");
showSeconds = settings.showSeconds !== undefined ? settings.showSeconds : false;
padHours12Hour = settings.padHours12Hour !== undefined ? settings.padHours12Hour : false;
greeterLockDateFormat = settings.greeterLockDateFormat !== undefined ? settings.greeterLockDateFormat : "";
greeterFontFamily = settings.greeterFontFamily !== undefined ? settings.greeterFontFamily : "";
greeterWallpaperFillMode = settings.greeterWallpaperFillMode !== undefined ? settings.greeterWallpaperFillMode : "";
useFahrenheit = settings.useFahrenheit !== undefined ? settings.useFahrenheit : false;
nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false;
weatherLocation = settings.weatherLocation !== undefined ? settings.weatherLocation : "New York, NY";
weatherCoordinates = settings.weatherCoordinates !== undefined ? settings.weatherCoordinates : "40.7128,-74.0060";
useAutoLocation = settings.useAutoLocation !== undefined ? settings.useAutoLocation : false;
weatherEnabled = settings.weatherEnabled !== undefined ? settings.weatherEnabled : true;
iconTheme = settings.iconTheme !== undefined ? settings.iconTheme : "System Default";
useOSLogo = settings.useOSLogo !== undefined ? settings.useOSLogo : false;
osLogoColorOverride = settings.osLogoColorOverride !== undefined ? settings.osLogoColorOverride : "";
osLogoBrightness = settings.osLogoBrightness !== undefined ? settings.osLogoBrightness : 0.5;
osLogoContrast = settings.osLogoContrast !== undefined ? settings.osLogoContrast : 1;
fontFamily = settings.fontFamily !== undefined ? settings.fontFamily : Theme.defaultFontFamily;
monoFontFamily = settings.monoFontFamily !== undefined ? settings.monoFontFamily : Theme.defaultMonoFontFamily;
fontWeight = settings.fontWeight !== undefined ? settings.fontWeight : Font.Normal;
fontScale = settings.fontScale !== undefined ? settings.fontScale : 1.0;
cornerRadius = settings.cornerRadius !== undefined ? settings.cornerRadius : 12;
widgetBackgroundColor = settings.widgetBackgroundColor !== undefined ? settings.widgetBackgroundColor : "sch";
lockDateFormat = settings.lockDateFormat !== undefined ? settings.lockDateFormat : "";
lockScreenShowPowerActions = settings.lockScreenShowPowerActions !== undefined ? settings.lockScreenShowPowerActions : true;
lockScreenShowProfileImage = settings.lockScreenShowProfileImage !== undefined ? settings.lockScreenShowProfileImage : true;
if (envRememberLastSession !== undefined) {
rememberLastSession = envRememberLastSession;
} else {
rememberLastSession = settings.greeterRememberLastSession !== undefined ? settings.greeterRememberLastSession : settings.rememberLastSession !== undefined ? settings.rememberLastSession : true;
}
if (envRememberLastUser !== undefined) {
rememberLastUser = envRememberLastUser;
} else {
rememberLastUser = settings.greeterRememberLastUser !== undefined ? settings.greeterRememberLastUser : settings.rememberLastUser !== undefined ? settings.rememberLastUser : true;
}
if (configBaseDir === root._greeterCacheDir) {
greeterAutoLogin = settings.greeterAutoLogin !== undefined ? settings.greeterAutoLogin : false;
}
greeterEnableFprint = settings.greeterEnableFprint !== undefined ? settings.greeterEnableFprint : false;
greeterEnableU2f = settings.greeterEnableU2f !== undefined ? settings.greeterEnableU2f : false;
greeterWallpaperPath = settings.greeterWallpaperPath !== undefined ? settings.greeterWallpaperPath : "";
powerActionConfirm = settings.powerActionConfirm !== undefined ? settings.powerActionConfirm : true;
powerActionHoldDuration = settings.powerActionHoldDuration !== undefined ? settings.powerActionHoldDuration : 0.5;
powerMenuActions = settings.powerMenuActions !== undefined ? settings.powerMenuActions : ["reboot", "logout", "poweroff", "lock", "suspend", "restart"];
powerMenuDefaultAction = settings.powerMenuDefaultAction !== undefined ? settings.powerMenuDefaultAction : "logout";
powerMenuGridLayout = settings.powerMenuGridLayout !== undefined ? settings.powerMenuGridLayout : false;
screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({});
animationSpeed = settings.animationSpeed !== undefined ? settings.animationSpeed : 2;
wallpaperFillMode = settings.wallpaperFillMode !== undefined ? settings.wallpaperFillMode : "Fill";
wallpaperBackgroundColorMode = settings.wallpaperBackgroundColorMode !== undefined ? settings.wallpaperBackgroundColorMode : "black";
wallpaperBackgroundCustomColor = settings.wallpaperBackgroundCustomColor !== undefined ? settings.wallpaperBackgroundCustomColor : "#000000";
if (typeof Theme !== "undefined") {
if (currentThemeName === "custom" && customThemeFile) {
Theme.loadCustomThemeFromFile(customThemeFile);
}
Theme.applyGreeterTheme(currentThemeName);
}
} catch (e) {
log.warn("Failed to parse greetd settings:", e);
} finally {
settingsLoaded = true;
}
}
function getEffectiveTimeFormat() {
const use24 = use24HourClock;
const secs = showSeconds;
const pad = padHours12Hour;
if (use24)
return secs ? "hh:mm:ss" : "hh:mm";
if (pad)
return secs ? "hh:mm:ss AP" : "hh:mm AP";
return secs ? "h:mm:ss AP" : "h:mm AP";
}
function getEffectiveLockDateFormat() {
const fmt = (greeterLockDateFormat !== undefined && greeterLockDateFormat !== "") ? greeterLockDateFormat : lockDateFormat;
return fmt && fmt.length > 0 ? fmt : Locale.LongFormat;
}
function getEffectiveWallpaperFillMode() {
return (greeterWallpaperFillMode && greeterWallpaperFillMode !== "") ? greeterWallpaperFillMode : wallpaperFillMode;
}
function getEffectiveFontFamily() {
return (greeterFontFamily && greeterFontFamily !== "") ? greeterFontFamily : fontFamily;
}
function getFilteredScreens(componentId) {
const prefs = screenPreferences && screenPreferences[componentId] || ["all"];
if (prefs.includes("all")) {
return Quickshell.screens;
}
return Quickshell.screens.filter(screen => prefs.includes(screen.name));
}
FileView {
id: settingsFile
path: root.configPath
blockLoading: false
blockWrites: true
atomicWrites: false
watchChanges: false
printErrors: false
onLoaded: {
parseSettings(settingsFile.text());
}
onLoadFailed: {
root.parseSettings("");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,36 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
Singleton {
id: root
property string passwordBuffer: ""
property string username: ""
property string usernameInput: ""
property bool showPasswordInput: false
property string selectedSession: ""
property string selectedSessionPath: ""
property string selectedSessionDesktopId: ""
property string pamState: ""
property bool unlocking: false
property var sessionList: []
property var sessionExecs: []
property var sessionPaths: []
property var sessionDesktopIds: []
property int currentSessionIndex: 0
property bool sessionManuallySelected: false
property var availableUsers: []
property int selectedUserIndex: -1
function reset() {
showPasswordInput = false;
username = "";
usernameInput = "";
passwordBuffer = "";
pamState = "";
selectedUserIndex = -1;
}
}
@@ -0,0 +1,33 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Common
Variants {
model: Quickshell.screens
PanelWindow {
id: root
property var modelData
screen: modelData
anchors {
left: true
right: true
top: true
bottom: true
}
exclusionMode: ExclusionMode.Normal
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
color: "transparent"
GreeterContent {
anchors.fill: parent
screenName: root.screen?.name ?? ""
}
}
}
@@ -0,0 +1,240 @@
import QtQuick
import QtQuick.Layouts
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true
property bool expanded: false
property int maxExpandedHeight: 400
property bool autoLoginVisible: false
property bool autoLoginChecked: false
property bool manualEntryVisible: false
signal userSelected(string username)
signal toggleRequested
signal autoLoginToggled
signal manualEntryRequested
readonly property int rowHeight: 52
readonly property int collapsedBarHeight: 36
readonly property int actionRowHeight: 44
readonly property int userListFullHeight: {
const count = GreeterUsersService.users.length;
if (count === 0)
return 0;
return count * rowHeight + Math.max(0, count - 1) * Theme.spacingXS;
}
readonly property int manualEntryBlockHeight: manualEntryVisible ? actionRowHeight + Theme.spacingXS : 0
readonly property int autoLoginBlockHeight: autoLoginVisible ? actionRowHeight + Theme.spacingXS : 0
readonly property int expandedContentHeight: {
if (!expanded)
return 0;
if (GreeterUsersService.users.length === 0 && !autoLoginVisible && !manualEntryVisible)
return 0;
return Math.min(maxExpandedHeight, userListFullHeight + manualEntryBlockHeight + autoLoginBlockHeight);
}
function encodeFileUrl(path) {
if (!path)
return "";
return "file://" + path.split("/").map(s => encodeURIComponent(s)).join("/");
}
function profileImageSource(username) {
const path = GreeterUsersService.profileImagePath(username);
if (path)
return encodeFileUrl(path);
return "";
}
implicitHeight: expanded ? expandedContentHeight : collapsedBarHeight
implicitWidth: parent ? parent.width : 320
Item {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
height: collapsedBarHeight
visible: !expanded
RowLayout {
anchors.fill: parent
spacing: Theme.spacingM
StyledText {
Layout.fillWidth: true
text: GreeterState.username ? GreeterUsersService.optionLabel(GreeterState.username) : I18n.tr("Select user...", "greeter user picker placeholder")
color: GreeterState.username ? Theme.surfaceText : Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeMedium
elide: Text.ElideRight
}
DankIcon {
Layout.alignment: Qt.AlignVCenter
name: "expand_more"
size: 20
color: Theme.surfaceVariantText
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: root.toggleRequested()
}
}
Column {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
height: root.expandedContentHeight
visible: expanded
spacing: Theme.spacingXS
DankListView {
id: userListView
width: parent.width
height: parent.height - root.manualEntryBlockHeight - root.autoLoginBlockHeight
clip: true
interactive: contentHeight > height
spacing: Theme.spacingXS
model: GreeterUsersService.users
delegate: Rectangle {
id: userRow
required property var modelData
required property int index
width: userListView.width
height: root.rowHeight
radius: Theme.cornerRadius
color: userRowMouse.containsMouse ? Theme.surfacePressed : Theme.withAlpha(Theme.surfacePressed, 0)
border.color: GreeterState.username === userRow.modelData.username ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
border.width: GreeterState.username === userRow.modelData.username ? 1 : 0
RowLayout {
anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
spacing: Theme.spacingM
Item {
Layout.preferredWidth: 36
Layout.preferredHeight: 36
DankCircularImage {
anchors.fill: parent
imageSource: root.profileImageSource(userRow.modelData.username)
fallbackIcon: "person"
}
}
StyledText {
Layout.fillWidth: true
text: GreeterUsersService.optionLabel(userRow.modelData.username)
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
elide: Text.ElideRight
}
}
MouseArea {
id: userRowMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.userSelected(userRow.modelData.username)
}
}
}
Rectangle {
width: parent.width
height: root.actionRowHeight
visible: root.manualEntryVisible
radius: Theme.cornerRadius
color: manualEntryRowMouse.containsMouse ? Theme.surfacePressed : Theme.withAlpha(Theme.surfacePressed, 0)
RowLayout {
anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
spacing: Theme.spacingM
DankIcon {
Layout.alignment: Qt.AlignVCenter
name: "person_add"
size: 20
color: Theme.surfaceVariantText
}
StyledText {
Layout.fillWidth: true
text: I18n.tr("Not listed?", "greeter link to switch to manual username entry")
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
elide: Text.ElideRight
}
}
MouseArea {
id: manualEntryRowMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.manualEntryRequested()
}
}
Rectangle {
width: parent.width
height: root.actionRowHeight
visible: root.autoLoginVisible
radius: Theme.cornerRadius
color: autoLoginRowMouse.containsMouse ? Theme.surfacePressed : Theme.withAlpha(Theme.surfacePressed, 0)
RowLayout {
anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
spacing: Theme.spacingM
DankIcon {
Layout.alignment: Qt.AlignVCenter
name: root.autoLoginChecked ? "check_box" : "check_box_outline_blank"
size: 20
color: root.autoLoginChecked ? Theme.primary : Theme.surfaceVariantText
}
StyledText {
Layout.fillWidth: true
text: I18n.tr("Auto-login")
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
elide: Text.ElideRight
}
}
MouseArea {
id: autoLoginRowMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.autoLoginToggled()
}
}
}
}
@@ -0,0 +1,51 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import qs.Common
import qs.Services
Singleton {
id: root
readonly property var log: Log.scoped("GreeterUserTheme")
readonly property string greetCfgDir: Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter"
property string activeUsername: ""
function userCacheDir(username) {
if (!username)
return "";
return greetCfgDir + "/users/" + username;
}
function applyForUser(username) {
const name = (username || "").trim();
activeUsername = name;
if (!name) {
applyDefault();
return;
}
const dir = userCacheDir(name);
if (typeof GreeterUsersService !== "undefined" && GreeterUsersService.hasSyncedTheme(name)) {
Theme.setGreeterColorsBaseDir(dir);
SessionData.setGreeterSessionBaseDir(dir);
GreetdSettings.setConfigBaseDir(dir);
return;
}
applyDefault();
}
function applyDefault() {
activeUsername = "";
Theme.resetGreeterColorsBaseDir();
SessionData.resetGreeterSessionBaseDir();
GreetdSettings.resetConfigBaseDir();
}
readonly property string activeWallpaperOverridePath: {
const base = activeUsername && typeof GreeterUsersService !== "undefined" && GreeterUsersService.hasSyncedTheme(activeUsername) ? userCacheDir(activeUsername) : greetCfgDir;
return base ? base + "/greeter_wallpaper_override.jpg" : "";
}
}
+7
View File
@@ -0,0 +1,7 @@
# Embedded Greetd module
TODO: remove when dank-greeter is available in the Arch extra repos.
The greeter moved to [dank-greeter](https://github.com/AvengeMedia/dank-greeter). This embedded copy exists only for archinstall compatibility: the `niri - DankMaterialShell` archinstall profile writes an `/etc/greetd/config.toml` that launches `Modules/Greetd/assets/dms-greeter` from the packaged DMS tree, and dank-greeter is not installable from the official Arch repos yet.
Do not extend this module. New greeter work goes to dank-greeter. Installing greetd-dms-greeter-bin from the AUR and running `dms-greeter sync` migrates a system off this embedded copy.
+503
View File
@@ -0,0 +1,503 @@
#!/bin/bash
set -e
COMPOSITOR=""
COMPOSITOR_CONFIG=""
DMS_PATH="dms-greeter"
CACHE_DIR="/var/cache/dms-greeter"
REMEMBER_LAST_SESSION=""
REMEMBER_LAST_USER=""
DEBUG_MODE=0
show_help() {
cat << EOF
dms-greeter - DankMaterialShell greeter launcher
Usage: dms-greeter --command COMPOSITOR [OPTIONS]
Required:
--command COMPOSITOR Compositor to use (niri, hyprland, sway, scroll, miracle, mango, or labwc)
Options:
-C, --config PATH Custom compositor config file
-p, --path PATH DMS path (config name or absolute path)
(default: dms-greeter)
--cache-dir PATH Cache directory for greeter data
(default: /var/cache/dms-greeter)
--remember-last-session BOOL
Persist selected session to greeter memory
(BOOL: true/false, default: from settings.json)
--remember-last-user BOOL
Persist last successful username to greeter memory
(BOOL: true/false, default: from settings.json)
--no-save-session Alias for --remember-last-session false
--no-save-username Alias for --remember-last-user false
--debug Enable verbose startup logging to stderr
-h, --help Show this help message
Examples:
dms-greeter --command niri
dms-greeter --command hyprland -C /etc/greetd/custom-hypr.conf
dms-greeter --command sway -p /home/user/.config/quickshell/custom-dms
dms-greeter --command scroll -p /home/user/.config/quickshell/custom-dms
dms-greeter --command niri --cache-dir /tmp/dmsgreeter
dms-greeter --command niri --no-save-session --no-save-username
dms-greeter --command mango
dms-greeter --command labwc
EOF
}
require_command() {
local cmd="$1"
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "Error: required command '$cmd' was not found in PATH" >&2
exit 1
fi
}
normalize_bool_flag() {
local flag_name="$1"
local value="$2"
local normalized="${value,,}"
case "$normalized" in
1|true|yes|on)
echo "1"
;;
0|false|no|off)
echo "0"
;;
*)
echo "Error: $flag_name must be true/false (or 1/0, yes/no, on/off)" >&2
exit 1
;;
esac
}
is_hyprland_lua_config() {
local config_file="$1"
case "$config_file" in
*.lua)
return 0
;;
esac
if [[ -f "$config_file" ]] && grep -qE '(^|[^[:alnum:]_])hl\.' "$config_file"; then
return 0
fi
return 1
}
clear_vt() {
local clear_sequence=$'\033[2J\033[H\033[3J\033[?25l'
# Clear retained console text on the controlling VT.
if printf '%s' "$clear_sequence" >/dev/tty 2>/dev/null; then
return
fi
if [[ -t 1 ]]; then
printf '%s' "$clear_sequence"
fi
}
exec_compositor() {
local log_tag="$1"
shift
if [[ "$DEBUG_MODE" == "1" ]]; then
exec "$@"
fi
clear_vt
if command -v systemd-cat >/dev/null 2>&1; then
exec "$@" > >(systemd-cat -t "dms-greeter/$log_tag" -p info 2>/dev/null) 2>&1
fi
# Preserve diagnostics on systems without journald.
exec "$@" >>"$CACHE_DIR/$log_tag.log" 2>&1
}
while [[ $# -gt 0 ]]; do
case $1 in
--command)
COMPOSITOR="$2"
shift 2
;;
-C|--config)
COMPOSITOR_CONFIG="$2"
shift 2
;;
-p|--path)
DMS_PATH="$2"
shift 2
;;
--cache-dir)
CACHE_DIR="$2"
shift 2
;;
--remember-last-session)
REMEMBER_LAST_SESSION="$2"
shift 2
;;
--remember-last-user)
REMEMBER_LAST_USER="$2"
shift 2
;;
--no-save-session)
REMEMBER_LAST_SESSION="0"
shift
;;
--no-save-username)
REMEMBER_LAST_USER="0"
shift
;;
--debug)
DEBUG_MODE=1
shift
;;
-h|--help)
show_help
exit 0
;;
*)
echo "Unknown option: $1" >&2
show_help
exit 1
;;
esac
done
if [[ -z "$COMPOSITOR" ]]; then
echo "Error: --command COMPOSITOR is required" >&2
show_help
exit 1
fi
locate_dms_config() {
local config_name="$1"
local search_paths=()
local config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
search_paths+=("$config_home/quickshell/$config_name")
search_paths+=("/usr/share/quickshell/$config_name")
local config_dirs="${XDG_CONFIG_DIRS:-/etc/xdg}"
IFS=':' read -ra dirs <<< "$config_dirs"
for dir in "${dirs[@]}"; do
if [[ -n "$dir" ]]; then
search_paths+=("$dir/quickshell/$config_name")
fi
done
for path in "${search_paths[@]}"; do
if [[ -f "$path/shell.qml" ]]; then
echo "$path"
return 0
fi
done
return 1
}
export XDG_SESSION_TYPE=wayland
export QT_QPA_PLATFORM=wayland
export QT_WAYLAND_DISABLE_WINDOWDECORATION=1
export EGL_PLATFORM=gbm
export DMS_RUN_GREETER=1
if [[ ! -d "$CACHE_DIR" ]]; then
echo "Error: cache directory '$CACHE_DIR' does not exist." >&2
echo " Run 'dms greeter sync' to initialize it, or pass --cache-dir to an existing directory." >&2
exit 1
fi
export DMS_GREET_CFG_DIR="$CACHE_DIR"
if [[ -n "$REMEMBER_LAST_SESSION" ]]; then
DMS_GREET_REMEMBER_LAST_SESSION=$(normalize_bool_flag "--remember-last-session" "$REMEMBER_LAST_SESSION")
export DMS_GREET_REMEMBER_LAST_SESSION
if [[ "$DMS_GREET_REMEMBER_LAST_SESSION" == "1" ]]; then
DMS_SAVE_SESSION=true
else
DMS_SAVE_SESSION=false
fi
export DMS_SAVE_SESSION
fi
if [[ -n "$REMEMBER_LAST_USER" ]]; then
DMS_GREET_REMEMBER_LAST_USER=$(normalize_bool_flag "--remember-last-user" "$REMEMBER_LAST_USER")
export DMS_GREET_REMEMBER_LAST_USER
if [[ "$DMS_GREET_REMEMBER_LAST_USER" == "1" ]]; then
DMS_SAVE_USERNAME=true
else
DMS_SAVE_USERNAME=false
fi
export DMS_SAVE_USERNAME
fi
export HOME="$CACHE_DIR"
export XDG_STATE_HOME="$CACHE_DIR/.local/state"
export XDG_DATA_HOME="$CACHE_DIR/.local/share"
export XDG_CACHE_HOME="$CACHE_DIR/.cache"
# Fallback runtime dir for systems without logind/pam_rundir (e.g. Void+seatd),
# where Wayland compositors abort with "RuntimeDirNotSet". Guarded so logind
# systems keep their own.
if [[ -z "${XDG_RUNTIME_DIR:-}" || ! -d "${XDG_RUNTIME_DIR:-}" ]]; then
export XDG_RUNTIME_DIR="$CACHE_DIR/run"
mkdir -p "$XDG_RUNTIME_DIR"
chmod 700 "$XDG_RUNTIME_DIR"
fi
# Keep greeter VT clean by default; callers can override via env or --debug.
if [[ -z "${RUST_LOG:-}" ]]; then
export RUST_LOG=warn
fi
if [[ -z "${NIRI_LOG:-}" ]]; then
export NIRI_LOG=warn
fi
# Export the user's configured cursor (from synced settings.json) for the
# compositor and greeter shell, but only if that theme is installed system-wide.
apply_user_cursor() {
command -v jq >/dev/null 2>&1 || return 0
local settings="$DMS_GREET_CFG_DIR/settings.json"
[[ -f "$settings" ]] || return 0
local theme size
theme=$(jq -r '.cursorSettings.theme // empty' "$settings" 2>/dev/null)
size=$(jq -r '.cursorSettings.size // empty' "$settings" 2>/dev/null)
[[ -n "$theme" && "$theme" != "System Default" ]] || return 0
local dirs=()
IFS=':' read -ra data_dirs <<< "${XDG_DATA_DIRS:-/usr/local/share:/usr/share}"
local d
for d in "${data_dirs[@]}"; do
[[ -n "$d" ]] && dirs+=("$d/icons")
done
dirs+=(/run/current-system/sw/share/icons /usr/share/icons /usr/local/share/icons)
for d in "${dirs[@]}"; do
if [[ -d "$d/$theme/cursors" ]]; then
export XCURSOR_PATH="$d${XCURSOR_PATH:+:$XCURSOR_PATH}"
export XCURSOR_THEME="$theme"
[[ -n "$size" ]] && export XCURSOR_SIZE="$size"
return 0
fi
done
}
apply_user_cursor
if command -v qs >/dev/null 2>&1; then
QS_BIN="qs"
elif command -v quickshell >/dev/null 2>&1; then
QS_BIN="quickshell"
else
echo "Error: neither 'qs' nor 'quickshell' was found in PATH" >&2
exit 1
fi
QS_CMD="$QS_BIN"
if [[ "$DMS_PATH" == /* ]]; then
QS_CMD="$QS_BIN -p $DMS_PATH"
else
RESOLVED_PATH=$(locate_dms_config "$DMS_PATH")
if [[ $? -eq 0 && -n "$RESOLVED_PATH" ]]; then
if [[ "$DEBUG_MODE" == "1" ]]; then
echo "Located DMS config at: $RESOLVED_PATH" >&2
fi
QS_CMD="$QS_BIN -p $RESOLVED_PATH"
else
echo "Error: Could not find DMS config '$DMS_PATH' (shell.qml) in any valid config path" >&2
exit 1
fi
fi
case "$COMPOSITOR" in
niri)
require_command "niri"
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
TEMP_CONFIG=$(mktemp)
# base/default config
cat > "$TEMP_CONFIG" << NIRI_EOF
hotkey-overlay {
skip-at-startup
}
environment {
DMS_RUN_GREETER "1"
}
gestures {
hot-corners {
off
}
}
layout {
background-color "#000000"
}
NIRI_EOF
COMPOSITOR_CONFIG="$TEMP_CONFIG"
else
TEMP_CONFIG=$(mktemp)
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
fi
# Append includes if override_files exist
OVERRIDE_FILES=(
/usr/share/greetd/niri_overrides.kdl # for building into a system image
/etc/greetd/niri_overrides.kdl # for local overrides
)
for override_file in "${OVERRIDE_FILES[@]}"; do
if [[ -e "$override_file" ]]; then
# TODO: maybe move to optional=true when Niri Next drops, so we can avoid these checks ;-)
cat >> "$TEMP_CONFIG" << NIRI_EOF
include "$override_file"
NIRI_EOF
fi
done
# Append spawn command
cat >> "$TEMP_CONFIG" << NIRI_EOF
spawn-at-startup "sh" "-c" "$QS_CMD; niri msg action quit --skip-confirmation"
NIRI_EOF
COMPOSITOR_CONFIG="$TEMP_CONFIG"
exec_compositor "niri" niri -c "$COMPOSITOR_CONFIG"
;;
hyprland)
if ! command -v start-hyprland >/dev/null 2>&1 && ! command -v Hyprland >/dev/null 2>&1; then
echo "Error: neither 'start-hyprland' nor 'Hyprland' was found in PATH" >&2
exit 1
fi
if [[ -n "$COMPOSITOR_CONFIG" ]] && is_hyprland_lua_config "$COMPOSITOR_CONFIG"; then
TEMP_CONFIG=$(mktemp --suffix=.lua)
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
cat >> "$TEMP_CONFIG" << HYPRLAND_LUA_EOF
hl.on("hyprland.start", function()
hl.exec_cmd('sh -c "$QS_CMD; hyprctl dispatch exit"')
end)
HYPRLAND_LUA_EOF
COMPOSITOR_CONFIG="$TEMP_CONFIG"
elif [[ -z "$COMPOSITOR_CONFIG" ]]; then
TEMP_CONFIG=$(mktemp --suffix=.lua)
cat > "$TEMP_CONFIG" << HYPRLAND_LUA_EOF
hl.env("DMS_RUN_GREETER", "1")
hl.config({
misc = {
disable_hyprland_logo = true,
},
})
hl.on("hyprland.start", function()
hl.exec_cmd('sh -c "$QS_CMD; hyprctl dispatch exit"')
end)
HYPRLAND_LUA_EOF
COMPOSITOR_CONFIG="$TEMP_CONFIG"
else
TEMP_CONFIG=$(mktemp)
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
cat >> "$TEMP_CONFIG" << HYPRLAND_EOF
exec-once = sh -c "$QS_CMD; hyprctl dispatch exit"
HYPRLAND_EOF
COMPOSITOR_CONFIG="$TEMP_CONFIG"
fi
if command -v start-hyprland >/dev/null 2>&1; then
exec_compositor "hyprland" start-hyprland -- --config "$COMPOSITOR_CONFIG"
else
exec_compositor "hyprland" Hyprland -c "$COMPOSITOR_CONFIG"
fi
;;
sway)
require_command "sway"
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
TEMP_CONFIG=$(mktemp)
cat > "$TEMP_CONFIG" << SWAY_EOF
exec "$QS_CMD; swaymsg exit"
SWAY_EOF
COMPOSITOR_CONFIG="$TEMP_CONFIG"
else
TEMP_CONFIG=$(mktemp)
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
cat >> "$TEMP_CONFIG" << SWAY_EOF
exec "$QS_CMD; swaymsg exit"
SWAY_EOF
COMPOSITOR_CONFIG="$TEMP_CONFIG"
fi
exec_compositor "sway" sway --unsupported-gpu -c "$COMPOSITOR_CONFIG"
;;
scroll)
require_command "scroll"
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
TEMP_CONFIG=$(mktemp)
cat > "$TEMP_CONFIG" << SCROLL_EOF
exec "$QS_CMD; scrollmsg exit"
SCROLL_EOF
COMPOSITOR_CONFIG="$TEMP_CONFIG"
else
TEMP_CONFIG=$(mktemp)
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
cat >> "$TEMP_CONFIG" << SCROLL_EOF
exec "$QS_CMD; scrollmsg exit"
SCROLL_EOF
COMPOSITOR_CONFIG="$TEMP_CONFIG"
fi
exec_compositor "scroll" scroll -c "$COMPOSITOR_CONFIG"
;;
miracle|miracle-wm)
require_command "miracle-wm"
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
TEMP_CONFIG=$(mktemp)
cat > "$TEMP_CONFIG" << MIRACLE_EOF
exec "$QS_CMD; miraclemsg exit"
MIRACLE_EOF
COMPOSITOR_CONFIG="$TEMP_CONFIG"
else
TEMP_CONFIG=$(mktemp)
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
cat >> "$TEMP_CONFIG" << MIRACLE_EOF
exec "$QS_CMD; miraclemsg exit"
MIRACLE_EOF
COMPOSITOR_CONFIG="$TEMP_CONFIG"
fi
exec_compositor "miracle" miracle-wm -c "$COMPOSITOR_CONFIG"
;;
labwc)
require_command "labwc"
if [[ -n "$COMPOSITOR_CONFIG" ]]; then
exec_compositor "labwc" labwc --config "$COMPOSITOR_CONFIG" --session "$QS_CMD"
else
exec_compositor "labwc" labwc --session "$QS_CMD"
fi
;;
mango|mangowc)
require_command "mango"
if [[ -n "$COMPOSITOR_CONFIG" ]]; then
exec_compositor "mango" mango -c "$COMPOSITOR_CONFIG" -s "$QS_CMD && mmsg dispatch quit"
else
exec_compositor "mango" mango -s "$QS_CMD && mmsg dispatch quit"
fi
;;
*)
echo "Error: Unsupported compositor: $COMPOSITOR" >&2
echo "Supported compositors: niri, hyprland, sway, scroll, miracle, mango, labwc" >&2
exit 1
;;
esac
@@ -0,0 +1,4 @@
# Deprecated: greetd expects Hyprland 0.55+ Lua; use `/etc/greetd/dms-hypr.lua` instead.
env = DMS_RUN_GREETER,1
exec = sh -c "qs -p _DMS_PATH_; hyprctl dispatch exit"
@@ -0,0 +1,8 @@
-- Minimal Hyprland (Lua) session for greetd — replace _DMS_PATH_ with your DMS checkout.
-- Copy to `/etc/greetd/dms-hypr.lua` alongside `greet-hyprland.sh`.
hl.env("DMS_RUN_GREETER", "1")
hl.on("hyprland.start", function()
hl.exec_cmd('sh -c "qs -p _DMS_PATH_; hyprctl dispatch exit"')
end)
@@ -0,0 +1,19 @@
hotkey-overlay {
skip-at-startup
}
environment {
DMS_RUN_GREETER "1"
}
spawn-at-startup "sh" "-c" "qs -p _DMS_PATH_; niri msg action quit --skip-confirmation"
gestures {
hot-corners {
off
}
}
layout {
background-color "#000000"
}
+11
View File
@@ -0,0 +1,11 @@
#!/bin/sh
export XDG_SESSION_TYPE=wayland
export QT_QPA_PLATFORM=wayland
export QT_WAYLAND_DISABLE_WINDOWDECORATION=1
export EGL_PLATFORM=gbm
if command -v start-hyprland >/dev/null 2>&1; then
exec start-hyprland -- -c /etc/greetd/dms-hypr.lua
else
exec Hyprland -c /etc/greetd/dms-hypr.lua
fi
+8
View File
@@ -0,0 +1,8 @@
#!/bin/sh
export XDG_SESSION_TYPE=wayland
export QT_QPA_PLATFORM=wayland
export QT_WAYLAND_DISABLE_WINDOWDECORATION=1
export EGL_PLATFORM=gbm
exec niri -c /etc/greetd/dms-niri.kdl
+22 -5
View File
@@ -81,7 +81,8 @@ Item {
property bool greeterTerminalFallbackFromPrecheck: false
property bool greeterBinaryExists: false
property bool greeterEnabled: false
readonly property bool greeterInstalled: greeterBinaryExists || greeterEnabled
property bool embeddedGreeterConfigured: false
readonly property bool embeddedGreeterOnly: embeddedGreeterConfigured && !greeterBinaryExists
readonly property string greeterAction: greeterBinaryExists && !greeterEnabled ? "activate" : ""
readonly property bool greeterActionAvailable: greeterAction !== ""
@@ -92,6 +93,7 @@ Item {
function checkGreeterInstallState() {
greetdEnabledCheckProcess.running = true;
greeterBinaryCheckProcess.running = true;
embeddedGreeterCheckProcess.running = true;
}
function runGreeterStatus() {
@@ -124,6 +126,8 @@ Item {
}
function runGreeterSync() {
if (!greeterBinaryExists)
return;
greeterSyncStdout = "";
greeterSyncStderr = "";
greeterSudoProbeStderr = "";
@@ -164,8 +168,17 @@ Item {
onExited: exitCode => {
root.greeterBinaryExists = (exitCode === 0);
if (exitCode !== 0 && root.greeterStatusText === "")
root.greeterStatusText = I18n.tr("Failed to run 'dms-greeter status'. Ensure the dms-greeter package is installed.", "greeter status error");
}
}
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);
}
}
@@ -396,6 +409,10 @@ Item {
return I18n.tr("Checking...", "greeter status loading");
if (root.greeterStatusText !== "")
return root.greeterStatusText;
if (root.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 (!root.greeterBinaryExists && root.greeterEnabled)
return I18n.tr("dms-greeter is not installed. Install the dms-greeter package to manage the greeter.", "greeter status placeholder");
return I18n.tr("Click Refresh to check status.", "greeter status placeholder");
}
font.pixelSize: Theme.fontSizeSmall
@@ -437,7 +454,7 @@ Item {
iconName: "sync"
horizontalPadding: Theme.spacingL
onClicked: root.runGreeterSync()
enabled: root.greeterInstalled && !root.greeterSyncRunning && !root.greeterInstallActionRunning
enabled: root.greeterBinaryExists && !root.greeterSyncRunning && !root.greeterInstallActionRunning
}
}
}
@@ -661,7 +678,7 @@ Item {
Rectangle {
id: syncPendingPill
readonly property bool shown: SessionData.greeterSyncPending && root.greeterInstalled
readonly property bool shown: SessionData.greeterSyncPending && root.greeterBinaryExists
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
+162
View File
@@ -0,0 +1,162 @@
pragma Singleton
import QtQuick
import Quickshell
import qs.Common
Singleton {
id: root
readonly property var log: Log.scoped("GreeterUsersService")
readonly property string greetCfgDir: Quickshell.env("DMS_GREET_CFG_DIR") || "/var/cache/dms-greeter"
readonly property string usersCacheDir: greetCfgDir + "/users"
property var users: []
property var usernames: []
property var profileImageMap: ({})
property bool loaded: false
property bool refreshing: false
Component.onCompleted: refresh()
function refresh() {
if (refreshing)
return;
refreshing = true;
_loadUsers();
}
function displayName(username) {
const u = _findUser(username);
if (!u)
return username || "";
const gecos = (u.gecos || "").trim();
return gecos.length > 0 ? gecos : username;
}
function optionLabel(username) {
const label = displayName(username);
return label !== username ? label : username;
}
function usernameFromOptionLabel(label) {
for (let i = 0; i < users.length; i++) {
if (root.optionLabel(users[i].username) === label)
return users[i].username;
}
return label;
}
function hasSyncedTheme(username) {
if (!username)
return false;
return syncedThemePaths[username] === true;
}
property var syncedThemePaths: ({})
function userCacheDir(username) {
if (!username)
return "";
return usersCacheDir + "/" + username;
}
function syncedSettingsPath(username) {
const dir = userCacheDir(username);
return dir ? dir + "/settings.json" : "";
}
function _findUser(name) {
for (let i = 0; i < users.length; i++) {
if (users[i].username === name)
return users[i];
}
return null;
}
function _loadUsers() {
Proc.runCommand("greeterUsersService-loadUsers", ["sh", "-c", "getent passwd | awk -F: '$3>=1000 && $3<60000 && $1!=\"nobody\" && $7!~/(nologin|false)$/ && $6!=\"/var/empty\" {print $1\":\"$3\":\"$5\":\"$6\":\"$7}'"], (output, exitCode) => {
const lines = (output || "").trim().split("\n").filter(l => l.length > 0);
const list = [];
const names = [];
for (let i = 0; i < lines.length; i++) {
const parts = lines[i].split(":");
if (parts.length < 5)
continue;
const username = parts[0];
list.push({
username,
uid: parseInt(parts[1], 10),
gecos: (parts[2] || "").split(",")[0],
home: parts[3] || "",
shell: parts[4] || ""
});
names.push(username);
}
list.sort((a, b) => a.username.localeCompare(b.username));
names.sort((a, b) => a.localeCompare(b));
root.users = list;
root.usernames = names;
root.loaded = true;
root.refreshing = false;
_refreshSyncedThemeFlags();
_loadProfileIcons();
}, 0);
}
function _refreshSyncedThemeFlags() {
if (usernames.length === 0) {
syncedThemePaths = ({});
return;
}
const checks = usernames.map(u => `[ -f "${syncedSettingsPath(u)}" ] && echo "${u}:1" || echo "${u}:0"`).join("; ");
Proc.runCommand("greeterUsersService-syncedThemes", ["sh", "-c", checks], (output, exitCode) => {
const map = {};
const lines = (output || "").trim().split("\n").filter(l => l.length > 0);
for (let i = 0; i < lines.length; i++) {
const parts = lines[i].split(":");
if (parts.length >= 2)
map[parts[0]] = parts[1] === "1";
}
root.syncedThemePaths = map;
}, 0);
}
function profileImagePath(username) {
if (!username)
return "";
return profileImageMap[username] || "";
}
function _loadProfileIcons() {
if (users.length === 0) {
profileImageMap = ({});
return;
}
const script = users.map(u => {
const safeUser = u.username.replace(/'/g, "'\\''");
const safeHome = (u.home || "").replace(/'/g, "'\\''");
const cacheDir = usersCacheDir + "/" + u.username;
return `( icon=""; for f in "${cacheDir}/profile.jpg" "${cacheDir}/profile.jpeg" "${cacheDir}/profile.png" "${cacheDir}/profile.webp" "/var/lib/AccountsService/icons/${safeUser}" "${safeHome}/.face" "${safeHome}/.face.icon"; do if [ -f "$f" ] && [ -r "$f" ]; then icon="$f"; break; fi; done; echo "${u.username}:$icon" )`;
}).join("; ");
Proc.runCommand("greeterUsersService-profileIcons", ["sh", "-c", script], (output, exitCode) => {
const map = {};
const lines = (output || "").trim().split("\n").filter(l => l.length > 0);
for (let i = 0; i < lines.length; i++) {
const idx = lines[i].indexOf(":");
if (idx <= 0)
continue;
const user = lines[i].substring(0, idx);
const icon = lines[i].substring(idx + 1).trim();
map[user] = icon && icon.length > 0 ? icon : "";
}
for (let j = 0; j < users.length; j++) {
const u = users[j].username;
if (!(u in map))
map[u] = "";
}
root.profileImageMap = map;
}, 0);
}
}
+50
View File
@@ -53,6 +53,10 @@ Singleton {
profileImage = "";
return;
}
if (Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true") {
profileImage = "";
return;
}
if (!freedeskAvailable) {
profileImage = "";
@@ -298,6 +302,52 @@ Singleton {
});
}
property string pendingGreeterProfileUser: ""
function getGreeterUserProfileImage(username) {
if (!username) {
profileImage = "";
pendingGreeterProfileUser = "";
return;
}
if (typeof GreeterUsersService !== "undefined") {
const cachedPath = GreeterUsersService.profileImagePath(username);
if (cachedPath) {
profileImage = cachedPath;
pendingGreeterProfileUser = "";
return;
}
}
pendingGreeterProfileUser = username;
userProfileCheckProcess.command = ["bash", "-c", `uid=$(id -u ${username} 2>/dev/null) && [ -n "$uid" ] && dbus-send --system --print-reply --dest=org.freedesktop.Accounts /org/freedesktop/Accounts/User$uid org.freedesktop.DBus.Properties.Get string:org.freedesktop.Accounts.User string:IconFile 2>/dev/null | grep -oP 'string "\\K[^"]+' || echo ""`];
userProfileCheckProcess.running = true;
}
Process {
id: userProfileCheckProcess
command: []
running: false
stdout: StdioCollector {
onStreamFinished: {
const trimmed = text.trim();
if (trimmed && trimmed !== "" && !trimmed.includes("Error") && trimmed !== "/var/lib/AccountsService/icons/") {
root.profileImage = trimmed;
} else {
root.profileImage = "";
}
root.pendingGreeterProfileUser = "";
}
}
onExited: exitCode => {
if (exitCode !== 0 && root.pendingGreeterProfileUser !== "") {
root.profileImage = "";
root.pendingGreeterProfileUser = "";
}
}
}
Process {
id: colorSchemeDetector
command: ["bash", "-c", "command -v gsettings || command -v dconf"]
+6 -5
View File
@@ -5,6 +5,7 @@ import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Modules.Greetd
import "../Common/suncalc.js" as SunCalc
Singleton {
@@ -470,7 +471,7 @@ Singleton {
}
function getConfiguredLocationName() {
return SettingsData.weatherLocation;
return SessionData.isGreeterMode ? SessionData.weatherLocation : SettingsData.weatherLocation;
}
function setLocation(lat, lon, city, country) {
@@ -523,9 +524,9 @@ Singleton {
}
function updateLocation() {
const useAuto = SettingsData.useAutoLocation;
const coords = SettingsData.weatherCoordinates;
const cityName = SettingsData.weatherLocation;
const useAuto = SessionData.isGreeterMode ? GreetdSettings.useAutoLocation : SettingsData.useAutoLocation;
const coords = SessionData.isGreeterMode ? SessionData.weatherCoordinates : SettingsData.weatherCoordinates;
const cityName = SessionData.isGreeterMode ? SessionData.weatherLocation : SettingsData.weatherLocation;
if (useAuto) {
getLocationFromService();
@@ -1047,7 +1048,7 @@ Singleton {
Timer {
id: updateTimer
interval: nextInterval()
running: root.refCount > 0 && SettingsData.weatherEnabled
running: root.refCount > 0 && SettingsData.weatherEnabled && !SessionData.isGreeterMode
repeat: true
triggeredOnStart: true
onTriggered: {
@@ -103,6 +103,7 @@ fi
targets=(
"${quickshell_dir}/shell.qml"
"${quickshell_dir}/DMSShell.qml"
"${quickshell_dir}/DMSGreeter.qml"
)
qmllint_args=(
+12
View File
@@ -17,6 +17,7 @@ import qs.Services
ShellRoot {
id: entrypoint
readonly property bool runGreeter: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
readonly property bool disableHotReload: Quickshell.env("DMS_DISABLE_HOT_RELOAD") === "1" || Quickshell.env("DMS_DISABLE_HOT_RELOAD") === "true"
Component.onCompleted: {
@@ -28,12 +29,15 @@ ShellRoot {
DC.Log.backend = Log;
DC.Host.session = SessionService;
DC.Host.cache = CacheData;
if (entrypoint.runGreeter)
return;
// Build the polkit agent here, outside incubation: first-touching it from a Connections target during DMSShell's async load crashed QQmlConnections::connectSignalsToMethods.
void PolkitService.agent;
}
Loader {
id: wallpaperLoader
active: !entrypoint.runGreeter
asynchronous: false
sourceComponent: Scope {
@@ -49,6 +53,7 @@ ShellRoot {
Loader {
id: shellCoreLoader
active: !entrypoint.runGreeter
asynchronous: true
source: "ShellCore.qml"
onLoaded: dmsShellLoader.setSource("DMSShell.qml", {
@@ -60,4 +65,11 @@ ShellRoot {
id: dmsShellLoader
asynchronous: true
}
Loader {
id: dmsGreeterLoader
active: entrypoint.runGreeter
asynchronous: false
source: "DMSGreeter.qml"
}
}
@@ -87,6 +87,12 @@ def extract_qstr_strings(root_dir):
if relative_path.parts[0] == 'PLUGINS':
continue
# Modules/Greetd terms are owned by the dank-greeter repo (tagged
# dms-greeter in POEditor); the embedded copy exists only for
# archinstall compatibility and must not upload untagged duplicates.
if relative_path.parts[:2] == ('Modules', 'Greetd'):
continue
with open(qml_file, 'r', encoding='utf-8') as f:
for line_num, line in enumerate(f, 1):
for pattern, quote in qstr_patterns: