1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-01 19:18:28 -04:00

greeter: migrated dms-greeter to github/AvengeMedia/dank-greeter

This commit is contained in:
bbedward
2026-07-19 14:18:26 -04:00
parent 1402d2312a
commit a69acf171a
106 changed files with 441 additions and 14644 deletions
+6 -14
View File
@@ -13,8 +13,6 @@ Singleton {
readonly property int cacheConfigVersion: 1
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)
@@ -75,9 +73,7 @@ Singleton {
})
Component.onCompleted: {
if (!isGreeterMode) {
loadCache();
}
loadCache();
}
function loadCache() {
@@ -200,7 +196,7 @@ Singleton {
FileView {
id: launcherCacheFile
path: isGreeterMode ? "" : _stateDir + "/DankMaterialShell/launcher_cache.json"
path: _stateDir + "/DankMaterialShell/launcher_cache.json"
blockLoading: true
blockWrites: true
atomicWrites: true
@@ -210,20 +206,16 @@ Singleton {
FileView {
id: cacheFile
path: isGreeterMode ? "" : _stateDir + "/DankMaterialShell/cache.json"
path: _stateDir + "/DankMaterialShell/cache.json"
blockLoading: true
blockWrites: true
atomicWrites: true
watchChanges: !isGreeterMode
watchChanges: true
onLoaded: {
if (!isGreeterMode) {
parseCache(cacheFile.text());
}
parseCache(cacheFile.text());
}
onLoadFailed: error => {
if (!isGreeterMode) {
log.info("No cache file found, starting fresh");
}
log.info("No cache file found, starting fresh");
}
}
}
-107
View File
@@ -1,107 +0,0 @@
.pragma library
// English language name -> ISO 639-1 code, for abbreviating xkb layout names.
const LANG_CODES = {
"afrikaans": "af",
"albanian": "sq",
"amharic": "am",
"arabic": "ar",
"armenian": "hy",
"azerbaijani": "az",
"basque": "eu",
"belarusian": "be",
"bengali": "bn",
"bosnian": "bs",
"bulgarian": "bg",
"burmese": "my",
"catalan": "ca",
"chinese": "zh",
"croatian": "hr",
"czech": "cs",
"danish": "da",
"dutch": "nl",
"english": "en",
"esperanto": "eo",
"estonian": "et",
"filipino": "fil",
"finnish": "fi",
"french": "fr",
"galician": "gl",
"georgian": "ka",
"german": "de",
"greek": "el",
"gujarati": "gu",
"hausa": "ha",
"hebrew": "he",
"hindi": "hi",
"hungarian": "hu",
"icelandic": "is",
"igbo": "ig",
"indonesian": "id",
"irish": "ga",
"italian": "it",
"japanese": "ja",
"javanese": "jv",
"kannada": "kn",
"kazakh": "kk",
"khmer": "km",
"korean": "ko",
"kurdish": "ku",
"kyrgyz": "ky",
"lao": "lo",
"latvian": "lv",
"lithuanian": "lt",
"luxembourgish": "lb",
"macedonian": "mk",
"malay": "ms",
"malayalam": "ml",
"maltese": "mt",
"maori": "mi",
"marathi": "mr",
"mongolian": "mn",
"nepali": "ne",
"norwegian": "no",
"pashto": "ps",
"persian": "fa",
"iranian": "fa",
"farsi": "fa",
"polish": "pl",
"portuguese": "pt",
"punjabi": "pa",
"romanian": "ro",
"russian": "ru",
"serbian": "sr",
"sindhi": "sd",
"sinhala": "si",
"slovak": "sk",
"slovenian": "sl",
"somali": "so",
"spanish": "es",
"swahili": "sw",
"swedish": "sv",
"tajik": "tg",
"tamil": "ta",
"tatar": "tt",
"telugu": "te",
"thai": "th",
"tibetan": "bo",
"turkish": "tr",
"turkmen": "tk",
"ukrainian": "uk",
"urdu": "ur",
"uyghur": "ug",
"uzbek": "uz",
"vietnamese": "vi",
"welsh": "cy",
"yiddish": "yi",
"yoruba": "yo",
"zulu": "zu"
};
function layoutCode(layoutName) {
if (!layoutName)
return "";
const lang = layoutName.split(" ")[0].toLowerCase();
const code = LANG_CODES[lang] || lang.substring(0, 2);
return code.toUpperCase();
}
+8 -51
View File
@@ -16,7 +16,6 @@ Singleton {
readonly property int sessionConfigVersion: 3
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
property bool _parseError: false
property bool _hasLoaded: false
property bool _isReadOnly: false
@@ -227,9 +226,7 @@ Singleton {
property string settingsSidebarCollapsedIds: ","
Component.onCompleted: {
if (!isGreeterMode) {
loadSettings();
}
loadSettings();
}
property var _pendingMigration: null
@@ -238,11 +235,6 @@ 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;
@@ -280,7 +272,7 @@ Singleton {
_loadedSessionSnapshot = getCurrentSessionJson();
_hasLoaded = true;
if (!isGreeterMode && typeof Theme !== "undefined")
if (typeof Theme !== "undefined")
Theme.generateSystemThemesFromCurrentTheme();
if (typeof WallpaperCyclingService !== "undefined")
@@ -362,7 +354,7 @@ Singleton {
_loadedSessionSnapshot = getCurrentSessionJson();
_hasLoaded = true;
if (!isGreeterMode && typeof Theme !== "undefined")
if (typeof Theme !== "undefined")
Theme.generateSystemThemesFromCurrentTheme();
if (typeof WallpaperCyclingService !== "undefined")
@@ -386,7 +378,7 @@ Singleton {
}
function saveSettings() {
if (isGreeterMode || _parseError || !_hasLoaded)
if (_parseError || !_hasLoaded)
return;
settingsFile.setText(getCurrentSessionJson());
if (_isReadOnly)
@@ -1394,16 +1386,14 @@ Singleton {
FileView {
id: settingsFile
path: isGreeterMode ? "" : StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/DankMaterialShell/session.json"
path: StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/DankMaterialShell/session.json"
blockLoading: true
blockWrites: true
atomicWrites: true
watchChanges: !isGreeterMode
watchChanges: true
onLoaded: {
if (!isGreeterMode) {
_hasUnsavedChanges = false;
parseSettings(settingsFile.text());
}
_hasUnsavedChanges = false;
parseSettings(settingsFile.text());
}
onSaveFailed: error => {
root._isReadOnly = true;
@@ -1411,39 +1401,6 @@ 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
+14 -36
View File
@@ -17,8 +17,6 @@ Singleton {
readonly property int settingsConfigVersion: 12
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
enum Position {
Top,
Bottom,
@@ -1439,19 +1437,15 @@ Singleton {
signal workspaceIconsUpdated
function refreshAuthAvailability() {
if (isGreeterMode)
return;
Processes.detectAuthCapabilities();
}
Component.onCompleted: {
if (!isGreeterMode) {
Processes.settingsRoot = root;
loadSettings();
initializeListModels();
refreshAuthAvailability();
Processes.checkPluginSettings();
}
Processes.settingsRoot = root;
loadSettings();
initializeListModels();
refreshAuthAvailability();
Processes.checkPluginSettings();
}
function applyStoredTheme() {
@@ -1517,8 +1511,6 @@ Singleton {
}
function checkIconThemeDrift() {
if (isGreeterMode)
return;
if (resolveIconTheme() === "System Default")
return;
if (!lastAppliedIconTheme)
@@ -1674,8 +1666,6 @@ Singleton {
}
function scheduleAuthApply() {
if (isGreeterMode)
return;
Qt.callLater(() => {
Processes.settingsRoot = root;
Processes.scheduleAuthApply();
@@ -1683,8 +1673,6 @@ Singleton {
}
function scheduleGreeterAutoLoginSync() {
if (isGreeterMode)
return;
Qt.callLater(() => {
Processes.settingsRoot = root;
Processes.scheduleGreeterAutoLoginSync();
@@ -1692,8 +1680,6 @@ Singleton {
}
function markGreeterSyncPending(who, key, oldValue) {
if (isGreeterMode)
return;
if (!(key in greeterSyncBaseline)) {
var baseline = greeterSyncBaseline;
baseline[key] = oldValue;
@@ -3632,7 +3618,7 @@ Singleton {
FileView {
id: settingsFile
path: isGreeterMode ? "" : StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json"
path: StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json"
blockLoading: true
blockWrites: true
atomicWrites: true
@@ -3645,8 +3631,6 @@ Singleton {
settingsFileReloadDebounce.restart();
}
onLoaded: {
if (isGreeterMode)
return;
const wasLoaded = _hasLoaded;
const prevFrameEnabled = frameEnabled;
const prevFrameMode = frameMode;
@@ -3689,9 +3673,7 @@ Singleton {
updateFrameCompositorLayout();
}
onLoadFailed: error => {
if (!isGreeterMode) {
applyStoredTheme();
}
applyStoredTheme();
}
onSaveFailed: error => {
root._isReadOnly = true;
@@ -3702,24 +3684,20 @@ Singleton {
FileView {
id: pluginSettingsFile
path: isGreeterMode ? "" : pluginSettingsPath
path: pluginSettingsPath
blockLoading: true
blockWrites: true
atomicWrites: true
printErrors: false
watchChanges: !isGreeterMode
watchChanges: true
onLoaded: {
if (!isGreeterMode) {
parsePluginSettings(pluginSettingsFile.text());
}
parsePluginSettings(pluginSettingsFile.text());
}
onLoadFailed: error => {
if (!isGreeterMode) {
const msg = String(error || "");
if (!_isMissingPluginSettingsError(error))
log.warn("Failed to load plugin_settings.json. Error:", msg);
_resetPluginSettings();
}
const msg = String(error || "");
if (!_isMissingPluginSettingsError(error))
log.warn("Failed to load plugin_settings.json. Error:", msg);
_resetPluginSettings();
}
}
+22 -92
View File
@@ -8,7 +8,6 @@ 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 {
@@ -143,13 +142,11 @@ Singleton {
Component.onCompleted: {
Quickshell.execDetached(["mkdir", "-p", stateDir]);
if (typeof SessionData === "undefined" || !SessionData.isGreeterMode)
Quickshell.execDetached([shellDir + "/scripts/gtk.sh", configDir, "", shellDir, "assets-only"]);
Quickshell.execDetached([shellDir + "/scripts/gtk.sh", configDir, "", shellDir, "assets-only"]);
Proc.runCommand("matugenCheck", ["sh", "-c", "command -v matugen"], (output, code) => {
matugenAvailable = (code === 0) && !envDisableMatugen;
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
if (!matugenAvailable || isGreeterMode) {
if (!matugenAvailable) {
return;
}
@@ -428,13 +425,6 @@ Singleton {
DMSService.sendRequest("theme.auto.trigger", {});
}
function applyGreeterTheme(themeName) {
switchTheme(themeName, false, false);
if (themeName === dynamic && dynamicColorsFileView.path) {
dynamicColorsFileView.reload();
}
}
function getMatugenColor(path, fallback) {
const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark";
let cur = matugenColors && matugenColors.colors && matugenColors.colors[colorMode];
@@ -1222,26 +1212,11 @@ Singleton {
return presetMap[SettingsData.modalAnimationSpeed] ?? 150;
}
property real cornerRadius: {
if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") {
return GreetdSettings.cornerRadius;
}
return typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12;
}
property real cornerRadius: typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12
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 fontFamily: typeof SettingsData !== "undefined" ? resolvedFontFamily(SettingsData.fontFamily) : DankCommon.Fonts.sans
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;
}
property string monoFontFamily: typeof SettingsData !== "undefined" ? resolvedMonoFontFamily(SettingsData.monoFontFamily) : DankCommon.Fonts.mono
function resolvedFontFamily(family) {
if (family === defaultFontFamily)
@@ -1255,19 +1230,9 @@ Singleton {
return family;
}
property int fontWeight: {
if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") {
return GreetdSettings.fontWeight;
}
return typeof SettingsData !== "undefined" ? SettingsData.fontWeight : Font.Normal;
}
property int fontWeight: typeof SettingsData !== "undefined" ? SettingsData.fontWeight : Font.Normal
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 fontScale: typeof SettingsData !== "undefined" ? SettingsData.fontScale : 1.0
property real spacingXXS: 2
property real spacingXS: 4
@@ -1327,15 +1292,12 @@ Singleton {
currentThemeCategory = "generic";
}
}
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
if (savePrefs && typeof SettingsData !== "undefined" && !isGreeterMode) {
if (savePrefs && typeof SettingsData !== "undefined") {
SettingsData.set("currentThemeCategory", currentThemeCategory);
SettingsData.set("currentThemeName", currentTheme);
}
if (!isGreeterMode) {
generateSystemThemesFromCurrentTheme();
}
generateSystemThemesFromCurrentTheme();
}
function setLightMode(light, savePrefs = true, enableTransition = false) {
@@ -1347,18 +1309,15 @@ Singleton {
return;
}
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
if (savePrefs && typeof SessionData !== "undefined" && !isGreeterMode) {
if (savePrefs && typeof SessionData !== "undefined") {
SessionData.setLightMode(light);
}
if (!isGreeterMode) {
PortalService.setLightMode(light);
if (typeof SettingsData !== "undefined") {
SettingsData.updateCosmicThemeMode(light);
}
generateSystemThemesFromCurrentTheme();
PortalService.setLightMode(light);
if (typeof SettingsData !== "undefined") {
SettingsData.updateCosmicThemeMode(light);
}
generateSystemThemesFromCurrentTheme();
}
function toggleLightMode(savePrefs = true) {
@@ -1412,8 +1371,7 @@ Singleton {
if (themeData.variants.type === "multi" && themeData.variants.flavors && themeData.variants.accents) {
const defaults = themeData.variants.defaults || {};
const modeDefaults = defaults[colorMode] || defaults.dark || {};
const isGreeterMode = typeof SessionData !== "undefined" && SessionData.isGreeterMode;
const stored = isGreeterMode ? (GreetdSettings.registryThemeVariants[themeId]?.[colorMode] || modeDefaults) : (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, modeDefaults, colorMode) : modeDefaults);
const stored = 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);
@@ -1439,8 +1397,7 @@ Singleton {
}
if (themeData.variants.options && themeData.variants.options.length > 0) {
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 selectedVariantId = 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 || {};
@@ -1800,8 +1757,7 @@ Singleton {
}
function generateSystemThemesFromCurrentTheme() {
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
if (!matugenAvailable || isGreeterMode)
if (!matugenAvailable)
return;
_lastGenerateMs = Date.now();
@@ -1842,9 +1798,8 @@ Singleton {
const defaults = customThemeRawData.variants.defaults || {};
const darkDefaults = defaults.dark || {};
const lightDefaults = defaults.light || defaults.dark || {};
const isGreeterMode = typeof SessionData !== "undefined" && SessionData.isGreeterMode;
const storedDark = isGreeterMode ? (GreetdSettings.registryThemeVariants[themeId]?.dark || darkDefaults) : (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, darkDefaults, "dark") : darkDefaults);
const storedLight = isGreeterMode ? (GreetdSettings.registryThemeVariants[themeId]?.light || lightDefaults) : (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, lightDefaults, "light") : lightDefaults);
const storedDark = typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, darkDefaults, "dark") : darkDefaults;
const storedLight = typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, lightDefaults, "light") : lightDefaults;
const darkFlavorId = storedDark.flavor || darkDefaults.flavor || "";
const lightFlavorId = storedLight.flavor || lightDefaults.flavor || "";
const darkAccentId = storedDark.accent || darkDefaults.accent || "";
@@ -1864,8 +1819,7 @@ Singleton {
lightTheme = mergeColors(lightTheme, lightAccent[lightFlavor.id] || {});
}
} else if (customThemeRawData.variants.options) {
const isGreeterMode = typeof SessionData !== "undefined" && SessionData.isGreeterMode;
const selectedVariantId = isGreeterMode ? (typeof GreetdSettings.registryThemeVariants[themeId] === "string" ? GreetdSettings.registryThemeVariants[themeId] : customThemeRawData.variants.default) : (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeVariant(themeId, customThemeRawData.variants.default) : customThemeRawData.variants.default);
const selectedVariantId = typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeVariant(themeId, customThemeRawData.variants.default) : customThemeRawData.variants.default;
const variant = findVariant(customThemeRawData.variants.options, selectedVariantId);
if (variant) {
darkTheme = mergeColors(darkTheme, variant.dark || {});
@@ -2214,32 +2168,11 @@ 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: {
if (SessionData.isGreeterMode)
return root.greeterColorsBaseDir ? (root.greeterColorsBaseDir + "/colors.json") : "";
return stateDir + "/dms-colors.json";
}
path: stateDir + "/dms-colors.json"
blockLoading: false
watchChanges: !SessionData.isGreeterMode
watchChanges: true
function parseAndLoadColors() {
try {
@@ -2274,9 +2207,6 @@ Singleton {
if (currentTheme !== dynamic)
return;
if (SessionData.isGreeterMode)
return;
if (workerRunning) {
colorsReloadRetry.restart();
return;
+6 -6
View File
@@ -13,14 +13,14 @@ Singleton {
property var settingsRoot: null
onSettingsRootChanged: {
if (settingsRoot && !settingsRoot.isGreeterMode)
if (settingsRoot)
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 || settingsRoot.isGreeterMode)
if (!settingsRoot)
return;
greeterAutoLoginPendingCheckProcess.running = true;
}
@@ -287,7 +287,7 @@ Singleton {
property string authApplyTerminalFallbackStderr: ""
function scheduleAuthApply() {
if (!settingsRoot || settingsRoot.isGreeterMode)
if (!settingsRoot)
return;
authApplyQueued = true;
@@ -300,7 +300,7 @@ Singleton {
}
function beginAuthApply() {
if (!authApplyQueued || authApplyRunning || !settingsRoot || settingsRoot.isGreeterMode)
if (!authApplyQueued || authApplyRunning || !settingsRoot)
return;
authApplyQueued = false;
@@ -339,7 +339,7 @@ Singleton {
property string greeterAutoLoginSyncStderr: ""
function scheduleGreeterAutoLoginSync() {
if (!settingsRoot || settingsRoot.isGreeterMode)
if (!settingsRoot)
return;
greeterAutoLoginSyncQueued = true;
@@ -352,7 +352,7 @@ Singleton {
}
function beginGreeterAutoLoginSync() {
if (!greeterAutoLoginSyncQueued || greeterAutoLoginSyncRunning || !settingsRoot || settingsRoot.isGreeterMode)
if (!greeterAutoLoginSyncQueued || greeterAutoLoginSyncRunning || !settingsRoot)
return;
greeterAutoLoginSyncQueued = false;
-9
View File
@@ -1,9 +0,0 @@
import QtQuick
import Quickshell
import qs.Modules.Greetd
Scope {
id: root
GreeterSurface {}
}
@@ -7,12 +7,7 @@ import qs.Services
Variants {
readonly property var log: Log.scoped("BlurredWallpaperBackground")
model: {
if (SessionData.isGreeterMode) {
return Quickshell.screens;
}
return SettingsData.getFilteredScreens("wallpaper");
}
model: SettingsData.getFilteredScreens("wallpaper")
PanelWindow {
id: blurWallpaperWindow
+2 -2
View File
@@ -144,7 +144,7 @@ Item {
smooth: true
cache: true
sourceSize: root.blurTextureSize
fillMode: root.getFillMode(SessionData.isGreeterMode ? GreetdSettings.wallpaperFillMode : SessionData.getMonitorWallpaperFillMode(root.screenName))
fillMode: root.getFillMode(SessionData.getMonitorWallpaperFillMode(root.screenName))
onStatusChanged: {
if (status === Image.Error) {
@@ -166,7 +166,7 @@ Item {
smooth: true
cache: true
sourceSize: root.blurTextureSize
fillMode: root.getFillMode(SessionData.isGreeterMode ? GreetdSettings.wallpaperFillMode : SessionData.getMonitorWallpaperFillMode(root.screenName))
fillMode: root.getFillMode(SessionData.getMonitorWallpaperFillMode(root.screenName))
onStatusChanged: {
if (status === Image.Error) {
@@ -6,7 +6,7 @@ import qs.Common
import qs.Modules.Plugins
import qs.Services
import qs.Widgets
import "../../../Common/LayoutCodes.js" as LayoutCodes
import "../../../DankCommon/Common/LayoutCodes.js" as LayoutCodes
BasePill {
id: root
-20
View File
@@ -1,20 +0,0 @@
.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
@@ -1,162 +0,0 @@
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: {}
}
}
@@ -1,230 +0,0 @@
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
@@ -1,35 +0,0 @@
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 var availableUsers: []
property int selectedUserIndex: -1
function reset() {
showPasswordInput = false;
username = "";
usernameInput = "";
passwordBuffer = "";
pamState = "";
selectedUserIndex = -1;
}
}
@@ -1,34 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Greetd
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 ?? ""
}
}
}
@@ -1,240 +0,0 @@
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()
}
}
}
}
@@ -1,51 +0,0 @@
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" : "";
}
}
-320
View File
@@ -1,320 +0,0 @@
# Dank (dms) Greeter
A greeter for [greetd](https://github.com/kennylevinsen/greetd) that follows the aesthetics of the dms lock screen.
## Features
- **Multi user**: Login with any system user
- **dms sync**: Sync settings with dms for consistent styling between shell and greeter
- **Multiple compositors**: The `dms-greeter` wrapper supports niri, Hyprland, sway, scroll, miracle-wm, labwc, and mangowc.
- **Custom PAM**: Supports custom PAM configuration in `/etc/pam.d/greetd`
- **Session Memory**: Remembers last selected session and user
- Can be disabled via `settings.json` keys: `greeterRememberLastSession` and `greeterRememberLastUser`
## Installation
### Arch Linux
Arch linux users can install [greetd-dms-greeter-git](https://aur.archlinux.org/packages/greetd-dms-greeter-git) from the AUR.
```bash
paru -S greetd-dms-greeter-git
# Or with yay
yay -S greetd-dms-greeter-git
```
### Debian / openSUSE
Official packages are available from the [DankLinux OBS repository](https://software.opensuse.org/download/package?package=dms-greeter&project=home%3AAvengeMedia%3Adanklinux). Add the repo for your distribution and install:
```bash
# Debian 13
sudo apt install dms-greeter # after adding the repo
# openSUSE Tumbleweed
zypper install dms-greeter # after adding the repo
```
See the [Installation guide](https://danklinux.com/docs/dankgreeter/installation) for full repository setup.
If you previously installed manually, remove legacy files first:
```bash
sudo rm -f /usr/local/bin/dms-greeter
sudo rm -rf /etc/xdg/quickshell/dms-greeter
```
Then complete setup:
```bash
dms greeter enable
dms greeter sync
```
Fingerprint authentication is optional. Install your distribution's
fingerprint service/client and PAM integration before enabling it. Package
names vary by distribution; on Fedora the native stack is:
```bash
sudo dnf install fprintd fprintd-pam
fprintd-enroll
```
The distro `fprintd` package normally pulls in `libfprint` automatically. After
enrollment, enable fingerprint login in **Settings → Greeter**. The toggle
applies the shared PAM configuration automatically, so no additional sync is
needed after initial greeter setup. Use `dms auth sync` only to repair or apply
authentication manually without syncing the rest of the greeter.
greetd tries fingerprint and password sequentially. DMS-managed PAM allows two
scans for up to ten seconds before password fallback. Existing distro PAM
policy takes precedence, so fallback timing can differ.
Verify enrollment with `fprintd-list "$USER"`; pass a token such as
`fprintd-enroll -f right-thumb` to enroll a specific finger. Security keys
similarly require `pam_u2f` setup and key registration before enabling the
login toggle.
If `fprintd-enroll` reports **No devices available**, check the [libfprint
supported devices list](https://fprint.freedesktop.org/supported-devices.html).
Some Validity/Synaptics readers are not supported by the native stack,
regardless of distribution; the
[open-fprintd](https://github.com/uunicorn/open-fprintd) service with the
[python-validity](https://github.com/uunicorn/python-validity) driver may work
instead. Follow the driver's installation and firmware instructions, and do
not run the stock `fprintd` daemon alongside its replacement.
#### Syncing themes (Optional)
To sync your wallpaper and theme with the greeter login screen, follow the manual setup below:
##### Manual theme syncing
```bash
# Add yourself to greeter group
sudo usermod -aG greeter <username>
# Set ACLs to allow greeter to traverse your directories
setfacl -m u:greeter:x ~ ~/.config ~/.local ~/.cache ~/.local/state
# Set group ownership on config directories
sudo chgrp -R greeter ~/.config/DankMaterialShell
sudo chgrp -R greeter ~/.local/state/DankMaterialShell
sudo chgrp -R greeter ~/.cache/DankMaterialShell
sudo chmod -R g+rX ~/.config/DankMaterialShell ~/.cache/DankMaterialShell ~/.cache/quickshell
# Create symlinks
sudo ln -sf ~/.config/DankMaterialShell/settings.json /var/cache/dms-greeter/settings.json
sudo ln -sf ~/.local/state/DankMaterialShell/session.json /var/cache/dms-greeter/session.json
sudo ln -sf ~/.cache/DankMaterialShell/dms-colors.json /var/cache/dms-greeter/colors.json
# Logout and login for group membership to take effect
```
</details>
### Fedora / RHEL / Rocky / Alma
Install from COPR or build the RPM:
```bash
# From COPR (when available)
sudo dnf copr enable avenge/dms
sudo dnf install dms-greeter
# Or build locally
cd /path/to/DankMaterialShell
rpkg local
sudo rpm -ivh x86_64/dms-greeter-*.rpm
```
The package automatically:
- Creates the greeter user (via `systemd-sysusers` from `/usr/lib/sysusers.d/dms-greeter.conf` for atomic/immutable compatibility, with package script fallback)
- Sets up directories and permissions
- Configures greetd with auto-detected compositor
- Applies SELinux contexts
Then complete setup:
```bash
dms greeter enable
dms greeter sync
```
#### Syncing themes (Optional)
Run:
```bash
dms greeter sync
```
Then logout/login to see your wallpaper on the greeter.
### Automatic
The easiest thing is to run `dms greeter install` or `dms` for interactive installation.
On Debian/openSUSE, this now prefers the `dms-greeter` package when the OBS repo is configured.
### Manual (fallback only)
Use this only if no package is available for your distro.
1. Install `greetd` (in most distro's standard repositories) and `quickshell`
2. Create the greeter user (if not already created by greetd):
```bash
sudo groupadd -r greeter
sudo useradd -r -g greeter -d /var/lib/greeter -s /bin/bash -c "System Greeter" greeter
sudo mkdir -p /var/lib/greeter
sudo chown greeter:greeter /var/lib/greeter
```
3. Clone the dms project to `/etc/xdg/quickshell/dms-greeter`:
```bash
sudo git clone https://github.com/AvengeMedia/DankMaterialShell.git /etc/xdg/quickshell/dms-greeter
```
4. Copy `Modules/Greetd/assets/dms-greeter` to `/usr/local/bin/dms-greeter`:
```bash
sudo cp /etc/xdg/quickshell/dms-greeter/Modules/Greetd/assets/dms-greeter /usr/local/bin/dms-greeter
sudo chmod +x /usr/local/bin/dms-greeter
```
5. Create greeter cache directory with proper permissions:
```bash
sudo mkdir -p /var/cache/dms-greeter
sudo chown <greeter-user>:<greeter-group> /var/cache/dms-greeter
sudo chmod 2770 /var/cache/dms-greeter
```
6. Edit or create `/etc/greetd/config.toml`:
```toml
[terminal]
vt = 1
[default_session]
user = "greeter"
# Change compositor to another wrapper-supported compositor if preferred
command = "/usr/local/bin/dms-greeter --command niri"
```
7. Disable existing display manager and enable greetd:
```bash
sudo systemctl disable gdm sddm lightdm
sudo systemctl enable greetd
```
8. (Optional) Set up theme syncing using the manual ACL method described in the Configuration → Personalization section below
#### Legacy installation (deprecated)
If you prefer the old method with separate shell scripts and config files:
1. Copy `assets/dms-niri.kdl` or `assets/dms-hypr.lua` (legacy: `assets/dms-hypr.conf`) to `/etc/greetd`
2. Copy `assets/greet-niri.sh` or `assets/greet-hyprland.sh` to `/usr/local/bin/start-dms-greetd.sh`
3. Edit the config file and replace `_DMS_PATH_` with your DMS installation path
4. Configure greetd to use `/usr/local/bin/start-dms-greetd.sh`
### NixOS
To install the greeter on NixOS add the repo to your flake inputs as described in the readme. Then somewhere in your NixOS config add this to imports:
```nix
imports = [
inputs.dank-material-shell.nixosModules.greeter
]
```
Enable the greeter with this in your NixOS config:
```nix
programs.dank-material-shell.greeter = {
enable = true;
compositor.name = "niri"; # or set to hyprland
configHome = "/home/user"; # optionally copyies that users DMS settings (and wallpaper if set) to the greeters data directory as root before greeter starts
};
```
## Usage
### Using dms-greeter wrapper (recommended)
The `dms-greeter` wrapper simplifies running the greeter with any compositor:
```bash
dms-greeter --command niri
dms-greeter --command hyprland
dms-greeter --command sway
dms-greeter --command mangowc
dms-greeter --command niri -C /path/to/custom-niri.kdl
dms-greeter --command niri --remember-last-user false --remember-last-session false
```
Configure greetd to use it in `/etc/greetd/config.toml`:
```toml
[terminal]
vt = 1
[default_session]
user = "greeter"
command = "/usr/bin/dms-greeter --command niri"
```
### Manual usage
To run dms in greeter mode you can also manually set environment variables:
```bash
DMS_RUN_GREETER=1 qs -p /path/to/dms
```
### Configuration
#### Compositor
For current wrapper-based installs, the `dms-greeter` wrapper supports niri, hyprland, sway, scroll, miracle-wm, labwc, and mangowc.
Only niri currently has a generated greeter config path managed by `dms greeter sync`.
- niri: `dms greeter sync` writes the generated greeter config to `/etc/greetd/niri/config.kdl`. Add local manual tweaks in `/etc/greetd/niri_overrides.kdl`.
- Other wrapper-supported compositors use the wrapper-generated config by default. If you need a custom compositor config, add `-C /path/to/config` to the `dms-greeter` command in `/etc/greetd/config.toml`.
#### Personalization
The greeter can be personalized with wallpapers, themes, weather, clock formats, and more - configured exactly the same as dms.
**Easiest method (single user):** Run `dms greeter sync` to automatically sync your DMS theme with the greeter.
**Multi-user systems:** One **main admin** runs full sync once to set up greetd and the shared cache (`dms greeter sync`, or `dms greeter sync --local` when developing from a checkout). **Every other account**—including other admins—should only run:
```bash
dms greeter sync --profile
```
Before that, an administrator must add each user to the `greeter` group in **Settings → Users** (greeter toggle) or with `sudo usermod -aG greeter <username>`. Each added user must log out and back in before `--profile` will work.
Per-user settings are stored under `/var/cache/dms-greeter/users/<username>/` for the login picker; the root cache remains the default fallback and is owned by whoever ran full sync.
**Manual method:** You can manually synchronize configurations if you want greeter settings to always mirror your shell:
```bash
# Add yourself to the greeter group
sudo usermod -aG greeter $USER
# Set ACLs to allow greeter user to traverse your home directory
setfacl -m u:greeter:x ~ ~/.config ~/.local ~/.cache ~/.local/state
# Set group permissions on DMS directories
sudo chgrp -R greeter ~/.config/DankMaterialShell ~/.local/state/DankMaterialShell ~/.cache/quickshell
sudo chmod -R g+rX ~/.config/DankMaterialShell ~/.local/state/DankMaterialShell ~/.cache/quickshell
# Create symlinks for theme files
sudo ln -sf ~/.config/DankMaterialShell/settings.json /var/cache/dms-greeter/settings.json
sudo ln -sf ~/.local/state/DankMaterialShell/session.json /var/cache/dms-greeter/session.json
sudo ln -sf ~/.cache/DankMaterialShell/dms-colors.json /var/cache/dms-greeter/colors.json
# Logout and login for group membership to take effect
```
**Advanced:** You can override the configuration path with the `DMS_GREET_CFG_DIR` environment variable or the `--cache-dir` flag when using `dms-greeter`. The default is `/var/cache/dms-greeter`.
The cache directory should be owned by `<greeter-user>:<greeter-group>` with `2770` permissions. If the greeter user is not available yet, DMS falls back to `root:<greeter-group>`.
@@ -1,499 +0,0 @@
#!/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)
cat > "$TEMP_CONFIG" << HYPRLAND_EOF
env = DMS_RUN_GREETER,1
misc {
disable_hyprland_logo = true
}
exec-once = sh -c "$QS_CMD; hyprctl dispatch exit"
HYPRLAND_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
@@ -1,4 +0,0 @@
# 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"
@@ -1,8 +0,0 @@
-- 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)
@@ -1,19 +0,0 @@
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"
}
@@ -1,11 +0,0 @@
#!/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
@@ -1,8 +0,0 @@
#!/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
@@ -1,44 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
DankActionButton {
id: customButtonKeyboard
circular: false
property string text: ""
width: 40
height: 40
property bool isShift: false
color: Theme.surface
property bool isIcon: text === "keyboard_hide" || text === "Backspace" || text === "Enter"
DankIcon {
anchors.centerIn: parent
name: {
if (parent.text === "keyboard_hide")
return "keyboard_hide";
if (parent.text === "Backspace")
return "backspace";
if (parent.text === "Enter")
return "keyboard_return";
return "";
}
size: 20
color: Theme.surfaceText
visible: parent.isIcon
}
StyledText {
id: contentItem
anchors.centerIn: parent
text: parent.text
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeXLarge
font.weight: Font.Normal
visible: !parent.isIcon
}
}
-369
View File
@@ -1,369 +0,0 @@
import QtQuick
import qs.Common
Rectangle {
id: root
property Item target
height: 60 * 5
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
color: Theme.surfaceContainerHigh
signal dismissed
property double rowSpacing: 0.01 * width // horizontal spacing between keyboard
property double columnSpacing: 0.02 * height // vertical spacing between keyboard
property bool shift: false //Boolean for the shift state
property bool symbols: false //Boolean for the symbol state
property double columns: 10 // Number of column
property double rows: 4 // Number of row
property string strShift: '\u2191'
property string strBackspace: "Backspace"
property string strEnter: "Enter"
property string strClose: "keyboard_hide"
property var modelKeyboard: {
"row_1": [
{
"text": 'q',
"symbol": '1',
"width": 1
},
{
"text": 'w',
"symbol": '2',
"width": 1
},
{
"text": 'e',
"symbol": '3',
"width": 1
},
{
"text": 'r',
"symbol": '4',
"width": 1
},
{
"text": 't',
"symbol": '5',
"width": 1
},
{
"text": 'y',
"symbol": '6',
"width": 1
},
{
"text": 'u',
"symbol": '7',
"width": 1
},
{
"text": 'i',
"symbol": '8',
"width": 1
},
{
"text": 'o',
"symbol": '9',
"width": 1
},
{
"text": 'p',
"symbol": '0',
"width": 1
}
],
"row_2": [
{
"text": 'a',
"symbol": '-',
"width": 1
},
{
"text": 's',
"symbol": '/',
"width": 1
},
{
"text": 'd',
"symbol": ':',
"width": 1
},
{
"text": 'f',
"symbol": ';',
"width": 1
},
{
"text": 'g',
"symbol": '(',
"width": 1
},
{
"text": 'h',
"symbol": ')',
"width": 1
},
{
"text": 'j',
"symbol": '€',
"width": 1
},
{
"text": 'k',
"symbol": '&',
"width": 1
},
{
"text": 'l',
"symbol": '@',
"width": 1
}
],
"row_3": [
{
"text": strShift,
"symbol": strShift,
"width": 1.5
},
{
"text": 'z',
"symbol": '.',
"width": 1
},
{
"text": 'x',
"symbol": ',',
"width": 1
},
{
"text": 'c',
"symbol": '?',
"width": 1
},
{
"text": 'v',
"symbol": '!',
"width": 1
},
{
"text": 'b',
"symbol": "'",
"width": 1
},
{
"text": 'n',
"symbol": "%",
"width": 1
},
{
"text": 'm',
"symbol": '"',
"width": 1
},
{
"text": strBackspace,
"symbol": strBackspace,
"width": 1.5
}
],
"row_4": [
{
"text": strClose,
"symbol": strClose,
"width": 1.5
},
{
"text": "123",
"symbol": 'ABC',
"width": 1.5
},
{
"text": ' ',
"symbol": ' ',
"width": 4.5
},
{
"text": '.',
"symbol": '.',
"width": 1
},
{
"text": strEnter,
"symbol": strEnter,
"width": 1.5
}
]
}
//Here is the corresponding table between the ascii and the key event
property var tableKeyEvent: {
"_0": Qt.Key_0,
"_1": Qt.Key_1,
"_2": Qt.Key_2,
"_3": Qt.Key_3,
"_4": Qt.Key_4,
"_5": Qt.Key_5,
"_6": Qt.Key_6,
"_7": Qt.Key_7,
"_8": Qt.Key_8,
"_9": Qt.Key_9,
"_a": Qt.Key_A,
"_b": Qt.Key_B,
"_c": Qt.Key_C,
"_d": Qt.Key_D,
"_e": Qt.Key_E,
"_f": Qt.Key_F,
"_g": Qt.Key_G,
"_h": Qt.Key_H,
"_i": Qt.Key_I,
"_j": Qt.Key_J,
"_k": Qt.Key_K,
"_l": Qt.Key_L,
"_m": Qt.Key_M,
"_n": Qt.Key_N,
"_o": Qt.Key_O,
"_p": Qt.Key_P,
"_q": Qt.Key_Q,
"_r": Qt.Key_R,
"_s": Qt.Key_S,
"_t": Qt.Key_T,
"_u": Qt.Key_U,
"_v": Qt.Key_V,
"_w": Qt.Key_W,
"_x": Qt.Key_X,
"_y": Qt.Key_Y,
"_z": Qt.Key_Z,
"_←": Qt.Key_Backspace,
"_return": Qt.Key_Return,
"_ ": Qt.Key_Space,
"_-": Qt.Key_Minus,
"_/": Qt.Key_Slash,
"_:": Qt.Key_Colon,
"_;": Qt.Key_Semicolon,
"_(": Qt.Key_BracketLeft,
"_)": Qt.Key_BracketRight,
"_€": parseInt("20ac", 16) // I didn't find the appropriate Qt event so I used the hex format
,
"_&": Qt.Key_Ampersand,
"_@": Qt.Key_At,
'_"': Qt.Key_QuoteDbl,
"_.": Qt.Key_Period,
"_,": Qt.Key_Comma,
"_?": Qt.Key_Question,
"_!": Qt.Key_Exclam,
"_'": Qt.Key_Apostrophe,
"_%": Qt.Key_Percent,
"_*": Qt.Key_Asterisk
}
Item {
id: keyboard_container
anchors.left: parent.left
anchors.leftMargin: 5
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: 5
anchors.bottom: parent.bottom
anchors.bottomMargin: 5
//One column which contains 5 rows
Column {
spacing: columnSpacing
Row {
id: row_1
spacing: rowSpacing
Repeater {
model: modelKeyboard["row_1"]
delegate: CustomButtonKeyboard {
text: symbols ? modelData.symbol : shift ? modelData.text.toUpperCase() : modelData.text
width: modelData.width * keyboard_container.width / columns - rowSpacing
height: keyboard_container.height / rows - columnSpacing
onClicked: root.clicked(text)
}
}
}
Row {
id: row_2
spacing: rowSpacing
Repeater {
model: modelKeyboard["row_2"]
delegate: CustomButtonKeyboard {
text: symbols ? modelData.symbol : shift ? modelData.text.toUpperCase() : modelData.text
width: modelData.width * keyboard_container.width / columns - rowSpacing
height: keyboard_container.height / rows - columnSpacing
onClicked: root.clicked(text)
}
}
}
Row {
id: row_3
spacing: rowSpacing
Repeater {
model: modelKeyboard["row_3"]
delegate: CustomButtonKeyboard {
text: symbols ? modelData.symbol : shift ? modelData.text.toUpperCase() : modelData.text
width: modelData.width * keyboard_container.width / columns - rowSpacing
height: keyboard_container.height / rows - columnSpacing
isShift: shift && text === strShift
onClicked: root.clicked(text)
}
}
}
Row {
id: row_4
spacing: rowSpacing
Repeater {
model: modelKeyboard["row_4"]
delegate: CustomButtonKeyboard {
text: symbols ? modelData.symbol : shift ? modelData.text.toUpperCase() : modelData.text
width: modelData.width * keyboard_container.width / columns - rowSpacing
height: keyboard_container.height / rows - columnSpacing
onClicked: root.clicked(text)
}
}
}
}
}
signal clicked(string text)
Connections {
target: root
function onClicked(text) {
if (!keyboard_controller.target)
return;
if (text === strShift) {
root.shift = !root.shift;
} else if (text === '123') {
root.symbols = true;
} else if (text === 'ABC') {
root.symbols = false;
} else if (text === strEnter) {
if (keyboard_controller.target.accepted) {
keyboard_controller.target.accepted();
}
} else if (text === strClose) {
root.dismissed();
} else {
if (text === strBackspace) {
keyboard_controller.target.backspace();
} else {
var charToInsert = root.symbols ? text : (root.shift ? text.toUpperCase() : text);
keyboard_controller.target.insertText(charToInsert);
}
if (root.shift && text !== strShift)
root.shift = false;
}
}
}
}
@@ -1,41 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Services
Item {
id: keyboard_controller
readonly property var log: Log.scoped("KeyboardController")
// reference on the TextInput
property Item target
//Booléan on the state of the keyboard
property bool isKeyboardActive: false
property var rootObject
function show() {
if (!isKeyboardActive && keyboard === null) {
keyboard = keyboardComponent.createObject(keyboard_controller.rootObject);
keyboard.target = keyboard_controller.target;
keyboard.dismissed.connect(hide);
isKeyboardActive = true;
} else
log.debug("The keyboard is already shown");
}
function hide() {
if (isKeyboardActive && keyboard !== null) {
keyboard.destroy();
isKeyboardActive = false;
} else
log.debug("The keyboard is already hidden");
}
// private
property Item keyboard: null
Component {
id: keyboardComponent
Keyboard {}
}
}
-827
View File
@@ -1,827 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell.Widgets
import qs.Common
import qs.Services
import qs.Widgets
Rectangle {
id: root
property bool isVisible: false
property bool showLogout: true
property int selectedIndex: 0
property int selectedRow: 0
property int selectedCol: 0
property var visibleActions: []
property int gridColumns: 3
property int gridRows: 2
property bool useGridLayout: false
property string holdAction: ""
property int holdActionIndex: -1
property real holdProgress: 0
property bool showHoldHint: false
property var powerActionConfirmOverride: undefined
property var powerActionHoldDurationOverride: undefined
property var powerMenuActionsOverride: undefined
property var powerMenuDefaultActionOverride: undefined
property var powerMenuGridLayoutOverride: undefined
property var requiredActions: []
readonly property bool needsConfirmation: powerActionConfirmOverride !== undefined ? powerActionConfirmOverride : SettingsData.powerActionConfirm
readonly property int holdDurationMs: (powerActionHoldDurationOverride !== undefined ? powerActionHoldDurationOverride : SettingsData.powerActionHoldDuration) * 1000
signal closed
signal switchUserRequested
function updateVisibleActions() {
const allActions = powerMenuActionsOverride !== undefined ? powerMenuActionsOverride : ((typeof SettingsData !== "undefined" && SettingsData.powerMenuActions) ? SettingsData.powerMenuActions : ["logout", "suspend", "hibernate", "reboot", "poweroff"]);
const hibernateSupported = (typeof SessionService !== "undefined" && SessionService.hibernateSupported) || false;
let filtered = allActions.filter(action => {
if (action === "hibernate" && !hibernateSupported)
return false;
if (action === "lock")
return false;
if (action === "restart")
return false;
if (action === "logout" && !showLogout)
return false;
return true;
});
for (const action of requiredActions) {
if (!filtered.includes(action))
filtered.push(action);
}
visibleActions = filtered;
useGridLayout = powerMenuGridLayoutOverride !== undefined ? powerMenuGridLayoutOverride : ((typeof SettingsData !== "undefined" && SettingsData.powerMenuGridLayout !== undefined) ? SettingsData.powerMenuGridLayout : false);
if (!useGridLayout)
return;
const count = visibleActions.length;
if (count === 0) {
gridColumns = 1;
gridRows = 1;
return;
}
if (count <= 3) {
gridColumns = 1;
gridRows = count;
return;
}
if (count === 4) {
gridColumns = 2;
gridRows = 2;
return;
}
gridColumns = 3;
gridRows = Math.ceil(count / 3);
}
function getDefaultActionIndex() {
const defaultAction = powerMenuDefaultActionOverride !== undefined ? powerMenuDefaultActionOverride : ((typeof SettingsData !== "undefined" && SettingsData.powerMenuDefaultAction) ? SettingsData.powerMenuDefaultAction : "suspend");
const index = visibleActions.indexOf(defaultAction);
return index >= 0 ? index : 0;
}
function getActionAtIndex(index) {
if (index < 0 || index >= visibleActions.length)
return "";
return visibleActions[index];
}
function getActionData(action) {
switch (action) {
case "reboot":
return {
"icon": "restart_alt",
"label": I18n.tr("Reboot"),
"key": "R"
};
case "logout":
return {
"icon": "logout",
"label": I18n.tr("Log Out"),
"key": "X"
};
case "poweroff":
return {
"icon": "power_settings_new",
"label": I18n.tr("Power Off"),
"key": "P"
};
case "suspend":
return {
"icon": "bedtime",
"label": I18n.tr("Suspend"),
"key": "S"
};
case "hibernate":
return {
"icon": "ac_unit",
"label": I18n.tr("Hibernate"),
"key": "H"
};
case "switchuser":
return {
"icon": "switch_account",
"label": I18n.tr("Switch User"),
"key": "U"
};
default:
return {
"icon": "help",
"label": action,
"key": "?"
};
}
}
function actionNeedsConfirm(action) {
return action !== "lock" && action !== "restart";
}
function startHold(action, actionIndex) {
if (!needsConfirmation || !actionNeedsConfirm(action)) {
executeAction(action);
return;
}
holdAction = action;
holdActionIndex = actionIndex;
holdProgress = 0;
showHoldHint = false;
holdTimer.start();
}
function cancelHold() {
if (holdAction === "")
return;
const wasHolding = holdProgress > 0;
holdTimer.stop();
if (wasHolding && holdProgress < 1) {
showHoldHint = true;
hintTimer.restart();
}
holdAction = "";
holdActionIndex = -1;
holdProgress = 0;
}
function completeHold() {
if (holdProgress < 1) {
cancelHold();
return;
}
const action = holdAction;
holdTimer.stop();
holdAction = "";
holdActionIndex = -1;
holdProgress = 0;
executeAction(action);
}
function executeAction(action) {
if (!action)
return;
if (action === "switchuser") {
hide();
switchUserRequested();
return;
}
if (typeof SessionService === "undefined")
return;
hide();
switch (action) {
case "logout":
SessionService.logout();
break;
case "suspend":
SessionService.suspend();
break;
case "hibernate":
SessionService.hibernate();
break;
case "reboot":
SessionService.reboot();
break;
case "poweroff":
SessionService.poweroff();
break;
}
}
function selectOption(action, actionIndex) {
startHold(action, actionIndex !== undefined ? actionIndex : -1);
}
function show() {
holdAction = "";
holdActionIndex = -1;
holdProgress = 0;
showHoldHint = false;
updateVisibleActions();
const defaultIndex = getDefaultActionIndex();
if (useGridLayout) {
selectedRow = Math.floor(defaultIndex / gridColumns);
selectedCol = defaultIndex % gridColumns;
selectedIndex = defaultIndex;
} else {
selectedIndex = defaultIndex;
}
isVisible = true;
Qt.callLater(() => powerMenuFocusScope.forceActiveFocus());
}
function hide() {
cancelHold();
isVisible = false;
closed();
}
function handleListNavigation(event, isPressed) {
if (!isPressed) {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_R || event.key === Qt.Key_X || event.key === Qt.Key_S || event.key === Qt.Key_H || (event.key === Qt.Key_P && !(event.modifiers & Qt.ControlModifier))) {
cancelHold();
event.accepted = true;
}
return;
}
switch (event.key) {
case Qt.Key_Up:
case Qt.Key_Backtab:
selectedIndex = (selectedIndex - 1 + visibleActions.length) % visibleActions.length;
event.accepted = true;
break;
case Qt.Key_Down:
case Qt.Key_Tab:
selectedIndex = (selectedIndex + 1) % visibleActions.length;
event.accepted = true;
break;
case Qt.Key_Return:
case Qt.Key_Enter:
startHold(getActionAtIndex(selectedIndex), selectedIndex);
event.accepted = true;
break;
case Qt.Key_N:
if (event.modifiers & Qt.ControlModifier) {
selectedIndex = (selectedIndex + 1) % visibleActions.length;
event.accepted = true;
}
break;
case Qt.Key_P:
if (!(event.modifiers & Qt.ControlModifier)) {
if (visibleActions.includes("poweroff")) {
const idx = visibleActions.indexOf("poweroff");
startHold("poweroff", idx);
event.accepted = true;
}
} else {
selectedIndex = (selectedIndex - 1 + visibleActions.length) % visibleActions.length;
event.accepted = true;
}
break;
case Qt.Key_J:
if (event.modifiers & Qt.ControlModifier) {
selectedIndex = (selectedIndex + 1) % visibleActions.length;
event.accepted = true;
}
break;
case Qt.Key_K:
if (event.modifiers & Qt.ControlModifier) {
selectedIndex = (selectedIndex - 1 + visibleActions.length) % visibleActions.length;
event.accepted = true;
}
break;
case Qt.Key_R:
if (visibleActions.includes("reboot")) {
startHold("reboot", visibleActions.indexOf("reboot"));
event.accepted = true;
}
break;
case Qt.Key_X:
if (visibleActions.includes("logout")) {
startHold("logout", visibleActions.indexOf("logout"));
event.accepted = true;
}
break;
case Qt.Key_S:
if (visibleActions.includes("suspend")) {
startHold("suspend", visibleActions.indexOf("suspend"));
event.accepted = true;
}
break;
case Qt.Key_H:
if (visibleActions.includes("hibernate")) {
startHold("hibernate", visibleActions.indexOf("hibernate"));
event.accepted = true;
}
break;
}
}
function handleGridNavigation(event, isPressed) {
if (!isPressed) {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_R || event.key === Qt.Key_X || event.key === Qt.Key_S || event.key === Qt.Key_H || (event.key === Qt.Key_P && !(event.modifiers & Qt.ControlModifier))) {
cancelHold();
event.accepted = true;
}
return;
}
switch (event.key) {
case Qt.Key_Left:
selectedCol = (selectedCol - 1 + gridColumns) % gridColumns;
selectedIndex = selectedRow * gridColumns + selectedCol;
event.accepted = true;
break;
case Qt.Key_Right:
selectedCol = (selectedCol + 1) % gridColumns;
selectedIndex = selectedRow * gridColumns + selectedCol;
event.accepted = true;
break;
case Qt.Key_Up:
case Qt.Key_Backtab:
selectedRow = (selectedRow - 1 + gridRows) % gridRows;
selectedIndex = selectedRow * gridColumns + selectedCol;
event.accepted = true;
break;
case Qt.Key_Down:
case Qt.Key_Tab:
selectedRow = (selectedRow + 1) % gridRows;
selectedIndex = selectedRow * gridColumns + selectedCol;
event.accepted = true;
break;
case Qt.Key_Return:
case Qt.Key_Enter:
startHold(getActionAtIndex(selectedIndex), selectedIndex);
event.accepted = true;
break;
case Qt.Key_N:
if (event.modifiers & Qt.ControlModifier) {
selectedCol = (selectedCol + 1) % gridColumns;
selectedIndex = selectedRow * gridColumns + selectedCol;
event.accepted = true;
}
break;
case Qt.Key_P:
if (!(event.modifiers & Qt.ControlModifier)) {
if (visibleActions.includes("poweroff")) {
const idx = visibleActions.indexOf("poweroff");
startHold("poweroff", idx);
event.accepted = true;
}
} else {
selectedCol = (selectedCol - 1 + gridColumns) % gridColumns;
selectedIndex = selectedRow * gridColumns + selectedCol;
event.accepted = true;
}
break;
case Qt.Key_J:
if (event.modifiers & Qt.ControlModifier) {
selectedRow = (selectedRow + 1) % gridRows;
selectedIndex = selectedRow * gridColumns + selectedCol;
event.accepted = true;
}
break;
case Qt.Key_K:
if (event.modifiers & Qt.ControlModifier) {
selectedRow = (selectedRow - 1 + gridRows) % gridRows;
selectedIndex = selectedRow * gridColumns + selectedCol;
event.accepted = true;
}
break;
case Qt.Key_R:
if (visibleActions.includes("reboot")) {
startHold("reboot", visibleActions.indexOf("reboot"));
event.accepted = true;
}
break;
case Qt.Key_X:
if (visibleActions.includes("logout")) {
startHold("logout", visibleActions.indexOf("logout"));
event.accepted = true;
}
break;
case Qt.Key_S:
if (visibleActions.includes("suspend")) {
startHold("suspend", visibleActions.indexOf("suspend"));
event.accepted = true;
}
break;
case Qt.Key_H:
if (visibleActions.includes("hibernate")) {
startHold("hibernate", visibleActions.indexOf("hibernate"));
event.accepted = true;
}
break;
}
}
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.5)
visible: isVisible
z: 1000
MouseArea {
anchors.fill: parent
onClicked: root.hide()
}
Timer {
id: holdTimer
interval: 16
repeat: true
onTriggered: {
root.holdProgress = Math.min(1, root.holdProgress + (interval / root.holdDurationMs));
if (root.holdProgress >= 1) {
stop();
root.completeHold();
}
}
}
Timer {
id: hintTimer
interval: 2000
onTriggered: root.showHoldHint = false
}
FocusScope {
id: powerMenuFocusScope
anchors.fill: parent
focus: root.isVisible
onVisibleChanged: {
if (visible)
Qt.callLater(() => forceActiveFocus());
}
Keys.onEscapePressed: root.hide()
Keys.onPressed: event => {
if (event.isAutoRepeat) {
event.accepted = true;
return;
}
if (useGridLayout) {
handleGridNavigation(event, true);
} else {
handleListNavigation(event, true);
}
}
Keys.onReleased: event => {
if (event.isAutoRepeat) {
event.accepted = true;
return;
}
if (useGridLayout) {
handleGridNavigation(event, false);
} else {
handleListNavigation(event, false);
}
}
Rectangle {
anchors.centerIn: parent
width: useGridLayout ? Math.min(550, gridColumns * 180 + Theme.spacingS * (gridColumns - 1) + Theme.spacingL * 2) : 320
height: contentItem.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.surfaceContainer
border.color: Theme.outlineMedium
border.width: 1
Item {
id: contentItem
anchors.fill: parent
anchors.margins: Theme.spacingL
implicitHeight: headerRow.height + Theme.spacingM + (useGridLayout ? buttonGrid.implicitHeight : buttonColumn.implicitHeight) + (root.needsConfirmation ? hintRow.height + Theme.spacingM : 0)
Row {
id: headerRow
width: parent.width
height: 30
StyledText {
text: I18n.tr("Power Options")
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: parent.width - 150
height: 1
}
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: root.hide()
}
}
Grid {
id: buttonGrid
visible: useGridLayout
anchors.top: headerRow.bottom
anchors.topMargin: Theme.spacingM
anchors.horizontalCenter: parent.horizontalCenter
columns: root.gridColumns
columnSpacing: Theme.spacingS
rowSpacing: Theme.spacingS
width: parent.width
Repeater {
model: root.visibleActions
Rectangle {
id: gridButtonRect
required property int index
required property string modelData
readonly property var actionData: root.getActionData(modelData)
readonly property bool isSelected: root.selectedIndex === index
readonly property bool showWarning: modelData === "reboot" || modelData === "poweroff"
readonly property bool isHolding: root.holdActionIndex === index && root.holdProgress > 0
width: (contentItem.width - Theme.spacingS * (root.gridColumns - 1)) / root.gridColumns
height: 100
radius: Theme.cornerRadius
color: {
if (isSelected)
return Theme.primaryHover;
if (mouseArea.containsMouse)
return Theme.primaryHoverLight;
return Theme.surfaceHover;
}
border.color: isSelected ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
border.width: isSelected ? 2 : 0
ClippingRectangle {
anchors.fill: parent
radius: parent.radius
color: "transparent"
visible: gridButtonRect.isHolding
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: parent.width * root.holdProgress
color: {
if (gridButtonRect.modelData === "poweroff")
return Theme.errorSelected;
if (gridButtonRect.modelData === "reboot")
return Theme.withAlpha(Theme.warning, 0.3);
return Theme.primarySelected;
}
}
}
Column {
anchors.centerIn: parent
spacing: Theme.spacingS
DankIcon {
name: gridButtonRect.actionData.icon
size: Theme.iconSize + 8
color: {
if (gridButtonRect.showWarning && (mouseArea.containsMouse || gridButtonRect.isHolding)) {
return gridButtonRect.modelData === "poweroff" ? Theme.error : Theme.warning;
}
return Theme.surfaceText;
}
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: gridButtonRect.actionData.label
font.pixelSize: Theme.fontSizeMedium
color: {
if (gridButtonRect.showWarning && (mouseArea.containsMouse || gridButtonRect.isHolding)) {
return gridButtonRect.modelData === "poweroff" ? Theme.error : Theme.warning;
}
return Theme.surfaceText;
}
font.weight: Font.Medium
anchors.horizontalCenter: parent.horizontalCenter
}
Rectangle {
width: 20
height: 16
radius: 4
color: Theme.onSurface_12
anchors.horizontalCenter: parent.horizontalCenter
StyledText {
text: gridButtonRect.actionData.key
font.pixelSize: Theme.fontSizeSmall - 1
color: Theme.surfaceTextSecondary
font.weight: Font.Medium
anchors.centerIn: parent
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
root.selectedRow = Math.floor(index / root.gridColumns);
root.selectedCol = index % root.gridColumns;
root.selectedIndex = index;
root.startHold(modelData, index);
}
onReleased: root.cancelHold()
onCanceled: root.cancelHold()
}
}
}
}
Column {
id: buttonColumn
visible: !useGridLayout
anchors.top: headerRow.bottom
anchors.topMargin: Theme.spacingM
anchors.left: parent.left
anchors.right: parent.right
spacing: Theme.spacingS
Repeater {
model: root.visibleActions
Rectangle {
id: listButtonRect
required property int index
required property string modelData
readonly property var actionData: root.getActionData(modelData)
readonly property bool isSelected: root.selectedIndex === index
readonly property bool showWarning: modelData === "reboot" || modelData === "poweroff"
readonly property bool isHolding: root.holdActionIndex === index && root.holdProgress > 0
width: parent.width
height: 50
radius: Theme.cornerRadius
color: {
if (isSelected)
return Theme.primaryHover;
if (listMouseArea.containsMouse)
return Theme.primaryHoverLight;
return Theme.surfaceHover;
}
border.color: isSelected ? Theme.primary : Theme.withAlpha(Theme.primary, 0)
border.width: isSelected ? 2 : 0
ClippingRectangle {
anchors.fill: parent
radius: parent.radius
color: "transparent"
visible: listButtonRect.isHolding
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: parent.width * root.holdProgress
color: {
if (listButtonRect.modelData === "poweroff")
return Theme.errorSelected;
if (listButtonRect.modelData === "reboot")
return Theme.withAlpha(Theme.warning, 0.3);
return Theme.primarySelected;
}
}
}
Row {
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankIcon {
name: listButtonRect.actionData.icon
size: Theme.iconSize + 4
color: {
if (listButtonRect.showWarning && (listMouseArea.containsMouse || listButtonRect.isHolding)) {
return listButtonRect.modelData === "poweroff" ? Theme.error : Theme.warning;
}
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: listButtonRect.actionData.label
font.pixelSize: Theme.fontSizeMedium
color: {
if (listButtonRect.showWarning && (listMouseArea.containsMouse || listButtonRect.isHolding)) {
return listButtonRect.modelData === "poweroff" ? Theme.error : Theme.warning;
}
return Theme.surfaceText;
}
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
Rectangle {
width: 28
height: 20
radius: 4
color: Theme.onSurface_12
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: listButtonRect.actionData.key
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextSecondary
font.weight: Font.Medium
anchors.centerIn: parent
}
}
MouseArea {
id: listMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
root.selectedIndex = index;
root.startHold(modelData, index);
}
onReleased: root.cancelHold()
onCanceled: root.cancelHold()
}
}
}
}
Row {
id: hintRow
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: Theme.spacingS
spacing: Theme.spacingXS
visible: root.needsConfirmation
opacity: root.showHoldHint ? 1 : 0.5
Behavior on opacity {
NumberAnimation {
duration: 150
}
}
DankIcon {
name: root.showHoldHint ? "warning" : "touch_app"
size: Theme.fontSizeSmall
color: root.showHoldHint ? Theme.warning : Theme.surfaceTextSecondary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
readonly property real totalMs: root.holdDurationMs
readonly property int remainingMs: Math.ceil(totalMs * (1 - root.holdProgress))
readonly property real durationSec: root.holdDurationMs / 1000
text: {
if (root.showHoldHint)
return I18n.tr("Hold longer to confirm");
if (root.holdProgress > 0) {
if (totalMs < 1000)
return I18n.tr("Hold to confirm (%1 ms)").arg(remainingMs);
return I18n.tr("Hold to confirm (%1s)").arg(Math.ceil(remainingMs / 1000));
}
if (totalMs < 1000)
return I18n.tr("Hold to confirm (%1 ms)").arg(totalMs);
return I18n.tr("Hold to confirm (%1s)").arg(durationSec);
}
font.pixelSize: Theme.fontSizeSmall
color: root.showHoldHint ? Theme.warning : Theme.surfaceTextSecondary
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}
Component.onCompleted: updateVisibleActions()
}
@@ -12,7 +12,8 @@ import qs.Common
import qs.Modals
import qs.Services
import qs.Widgets
import "../../Common/LayoutCodes.js" as LayoutCodes
import qs.DankCommon.Session
import "../../DankCommon/Common/LayoutCodes.js" as LayoutCodes
Item {
id: root
+72 -40
View File
@@ -82,27 +82,44 @@ Item {
property bool greeterBinaryExists: false
property bool greeterEnabled: false
readonly property bool greeterInstalled: greeterBinaryExists || greeterEnabled
readonly property string greeterAction: {
if (!greeterInstalled)
return "install";
if (!greeterEnabled)
return "activate";
return "";
}
readonly property bool greeterActionAvailable: greeterAction !== ""
readonly property string greeterActionLabel: {
if (!root.greeterInstalled)
switch (greeterAction) {
case "install":
return I18n.tr("Install");
if (!root.greeterEnabled)
case "activate":
return I18n.tr("Activate");
return I18n.tr("Uninstall");
default:
return "";
}
}
readonly property string greeterActionIcon: {
if (!root.greeterInstalled)
switch (greeterAction) {
case "install":
return "download";
if (!root.greeterEnabled)
case "activate":
return "login";
return "delete";
default:
return "";
}
}
readonly property var greeterActionCommand: {
if (!root.greeterInstalled)
switch (greeterAction) {
case "install":
return ["dms", "greeter", "install", "--terminal"];
if (!root.greeterEnabled)
case "activate":
return ["dms", "greeter", "enable", "--terminal"];
return ["dms", "greeter", "uninstall", "--terminal", "--yes"];
default:
return [];
}
}
property string greeterPendingAction: ""
@@ -120,26 +137,28 @@ Item {
}
function runGreeterInstallAction() {
root.greeterPendingAction = !root.greeterInstalled ? "install" : !root.greeterEnabled ? "activate" : "uninstall";
root.greeterPendingAction = root.greeterAction;
greeterStatusText = I18n.tr("Opening terminal: ") + root.greeterActionLabel + "...";
greeterInstallActionRunning = true;
greeterInstallActionProcess.running = true;
}
function promptGreeterActionConfirm() {
if (!root.greeterActionAvailable)
return;
var title, message, confirmText;
if (!root.greeterInstalled) {
switch (root.greeterAction) {
case "install":
title = I18n.tr("Install Greeter", "greeter action confirmation");
message = I18n.tr("Install the DMS greeter? A terminal will open for sudo authentication.");
confirmText = I18n.tr("Install");
} else if (!root.greeterEnabled) {
break;
case "activate":
title = I18n.tr("Activate Greeter", "greeter action confirmation");
message = I18n.tr("Activate the DMS greeter? A terminal will open for sudo authentication. Run Sync after activation to apply your settings.");
confirmText = I18n.tr("Activate");
} else {
title = I18n.tr("Uninstall Greeter", "greeter action confirmation");
message = I18n.tr("Uninstall the DMS greeter? This will remove configuration and restore your previous display manager. A terminal will open for sudo authentication.");
confirmText = I18n.tr("Uninstall");
break;
}
greeterActionConfirm.showWithOptions({
"title": title,
@@ -247,16 +266,8 @@ Item {
root.greeterSyncRunning = false;
const out = (root.greeterSyncStdout || "").trim();
const err = (root.greeterSyncStderr || "").trim();
if (exitCode === 0) {
var success = I18n.tr("Sync completed successfully.");
if (out !== "")
success = success + "\n\n" + out;
if (err !== "")
success = success + "\n\nstderr:\n" + err;
root.greeterStatusText = success;
SettingsData.clearGreeterSyncPending();
ToastService.showInfo(I18n.tr("Greeter sync complete"));
} else {
root.checkGreeterInstallState();
if (exitCode !== 0) {
var failure = I18n.tr("Sync failed in background mode. Trying terminal mode so you can authenticate interactively.") + " (exit " + exitCode + ")";
if (out !== "")
failure = failure + "\n\n" + out;
@@ -264,8 +275,16 @@ Item {
failure = failure + "\n\nstderr:\n" + err;
root.greeterStatusText = failure;
root.launchGreeterSyncTerminalFallback(false, "");
return;
}
root.checkGreeterInstallState();
var success = I18n.tr("Sync completed successfully.");
if (out !== "")
success = success + "\n\n" + out;
if (err !== "")
success = success + "\n\nstderr:\n" + err;
root.greeterStatusText = success;
SettingsData.clearGreeterSyncPending();
ToastService.showInfo(I18n.tr("Greeter sync complete"));
}
}
@@ -327,17 +346,19 @@ Item {
root.greeterInstallActionRunning = false;
const pending = root.greeterPendingAction;
root.greeterPendingAction = "";
if (exitCode === 0) {
if (pending === "install")
root.greeterStatusText = I18n.tr("Install complete. Greeter has been installed.");
else if (pending === "activate")
root.greeterStatusText = I18n.tr("Greeter activated. greetd is now enabled.");
else
root.greeterStatusText = I18n.tr("Uninstall complete. Greeter has been removed.");
} else {
root.greeterStatusText = I18n.tr("Action failed or terminal was closed.") + " (exit " + exitCode + ")";
}
root.checkGreeterInstallState();
if (exitCode !== 0) {
root.greeterStatusText = I18n.tr("Action failed or terminal was closed.") + " (exit " + exitCode + ")";
return;
}
switch (pending) {
case "install":
root.greeterStatusText = I18n.tr("Install complete. Greeter has been installed.");
return;
default:
root.greeterStatusText = I18n.tr("Greeter activated. greetd is now enabled.");
return;
}
}
}
@@ -423,7 +444,13 @@ Item {
id: statusTextArea
anchors.fill: parent
anchors.margins: Theme.spacingM
text: root.greeterStatusRunning ? I18n.tr("Checking...", "greeter status loading") : (root.greeterStatusText || I18n.tr("Click Refresh to check status.", "greeter status placeholder"))
text: {
if (root.greeterStatusRunning)
return I18n.tr("Checking...", "greeter status loading");
if (root.greeterStatusText !== "")
return root.greeterStatusText;
return I18n.tr("Click Refresh to check status.", "greeter status placeholder");
}
font.pixelSize: Theme.fontSizeSmall
font.family: "monospace"
color: root.greeterStatusRunning ? Theme.surfaceVariantText : Theme.surfaceText
@@ -442,6 +469,7 @@ Item {
spacing: Theme.spacingS
DankButton {
visible: root.greeterActionAvailable
text: root.greeterActionLabel
iconName: root.greeterActionIcon
horizontalPadding: Theme.spacingL
@@ -556,9 +584,13 @@ Item {
description: I18n.tr("Format the date on the login screen")
options: root._lockDateFormatPresets.map(p => p.label)
currentValue: {
var current = (SettingsData.greeterLockDateFormat !== undefined && SettingsData.greeterLockDateFormat !== "") ? SettingsData.greeterLockDateFormat : SettingsData.lockDateFormat || "";
var current = SettingsData.greeterLockDateFormat || SettingsData.lockDateFormat || "";
var match = root._lockDateFormatPresets.find(p => p.format === current);
return match ? match.label : (current ? I18n.tr("Custom") + ": " + current : root._lockDateFormatPresets[0].label);
if (match)
return match.label;
if (current)
return I18n.tr("Custom") + ": " + current;
return root._lockDateFormatPresets[0].label;
}
onValueChanged: value => {
var preset = root._lockDateFormatPresets.find(p => p.label === value);
+1 -6
View File
@@ -13,12 +13,7 @@ Variants {
// An entry present in PanelWindow.onCompleted means we're recreating
// after a wl_output rebind, not at initial startup.
property var _seenScreens: ({})
model: {
if (SessionData.isGreeterMode) {
return Quickshell.screens;
}
return SettingsData.getFilteredScreens("wallpaper");
}
model: SettingsData.getFilteredScreens("wallpaper")
PanelWindow {
id: wallpaperWindow
-1
View File
@@ -42,7 +42,6 @@ make lint-qml # Run from repo root; requires quickshell/.qmlls.ini (generated b
**System Controls**
- `Modules/ControlCenter/` - WiFi, Bluetooth, audio, display settings
- `Modules/Notifications/` - Notification center with popups
- `Modules/Greetd/` - Login greeter interface
**Overlays**
- `Modules/Spotlight/` - Application and file launcher
-163
View File
@@ -1,163 +0,0 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
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,10 +53,6 @@ Singleton {
profileImage = "";
return;
}
if (Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true") {
profileImage = "";
return;
}
if (!freedeskAvailable) {
profileImage = "";
@@ -299,52 +295,6 @@ 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"]
+5 -6
View File
@@ -5,7 +5,6 @@ import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Modules.Greetd
import "../Common/suncalc.js" as SunCalc
Singleton {
@@ -463,7 +462,7 @@ Singleton {
}
function getConfiguredLocationName() {
return SessionData.isGreeterMode ? SessionData.weatherLocation : SettingsData.weatherLocation;
return SettingsData.weatherLocation;
}
function setLocation(lat, lon, city, country) {
@@ -516,9 +515,9 @@ Singleton {
}
function updateLocation() {
const useAuto = SessionData.isGreeterMode ? GreetdSettings.useAutoLocation : SettingsData.useAutoLocation;
const coords = SessionData.isGreeterMode ? SessionData.weatherCoordinates : SettingsData.weatherCoordinates;
const cityName = SessionData.isGreeterMode ? SessionData.weatherLocation : SettingsData.weatherLocation;
const useAuto = SettingsData.useAutoLocation;
const coords = SettingsData.weatherCoordinates;
const cityName = SettingsData.weatherLocation;
if (useAuto) {
getLocationFromService();
@@ -1024,7 +1023,7 @@ Singleton {
Timer {
id: updateTimer
interval: nextInterval()
running: root.refCount > 0 && SettingsData.weatherEnabled && !SessionData.isGreeterMode
running: root.refCount > 0 && SettingsData.weatherEnabled
repeat: true
triggeredOnStart: true
onTriggered: {
+34 -7
View File
@@ -104,6 +104,26 @@ def load_common_entries():
entries = json.load(f)
return [{**e, "tags": sorted(set(e.get("tags", [])) | {"dank-qml-common"})} for e in entries]
# dms-greeter terms live in this POEditor project but are owned by the
# dank-greeter repo. They must ride along in every upload so prune
# (sync_terms) does not delete them and tag-less uploads do not strip
# their tag.
GREETER_TAG = "dms-greeter"
def load_greeter_entries(api_token, project_id):
resp = poeditor_request('terms/list', {
'api_token': api_token,
'id': project_id
})
if resp.get('response', {}).get('status') != 'success':
error(f"POEditor terms list failed: {resp}")
terms = resp.get('result', {}).get('terms', [])
return [
{'term': t['term'], 'context': t.get('context', ''), 'tags': sorted(set(t.get('tags', [])))}
for t in terms
if GREETER_TAG in t.get('tags', [])
]
def entry_keys(entries):
return {(e.get('context') or e['term'], e['term']) for e in entries}
@@ -118,14 +138,15 @@ def combine_entries(app_entries, common_entries):
combined.append(entry)
return combined + list(common_by_key.values())
def split_export(data, common_keys):
def split_export(data, common_keys, greeter_keys):
app_part = {}
common_part = {}
for context, terms in data.items():
if not isinstance(terms, dict):
continue
common_terms = {t: v for t, v in terms.items() if (context, t) in common_keys}
app_terms = {t: v for t, v in terms.items() if (context, t) not in common_keys}
app_terms = {t: v for t, v in terms.items()
if (context, t) not in common_keys and (context, t) not in greeter_keys}
if common_terms:
common_part[context] = common_terms
if app_terms:
@@ -197,7 +218,7 @@ def write_if_changed(repo_file, new_data):
f.write('\n')
return True
def download_translations(api_token, project_id, common_keys):
def download_translations(api_token, project_id, common_keys, greeter_keys):
info("Downloading translations from POEditor...")
POEXPORTS_DIR.mkdir(parents=True, exist_ok=True)
@@ -234,7 +255,7 @@ def download_translations(api_token, project_id, common_keys):
warn(f"Failed to download {po_lang}: {e}")
continue
app_part, common_part = split_export(new_data, common_keys)
app_part, common_part = split_export(new_data, common_keys, greeter_keys)
if write_if_changed(repo_file, app_part):
success(f"Updated {filename}")
@@ -257,6 +278,7 @@ def check_sync_status():
current_en = normalize_json(EN_JSON)
common_entries = load_common_entries()
common_keys = entry_keys(common_entries)
greeter_keys = entry_keys(load_greeter_entries(api_token, project_id))
if not SYNC_STATE.exists():
return True
@@ -294,7 +316,7 @@ def check_sync_status():
try:
with request.urlopen(url) as response:
remote_data = json.loads(response.read().decode())
app_part, common_part = split_export(remote_data, common_keys)
app_part, common_part = split_export(remote_data, common_keys, greeter_keys)
first_file = LANGUAGES[list(LANGUAGES.keys())[0]]
if json_changed(POEXPORTS_DIR / first_file, app_part):
@@ -366,9 +388,12 @@ def main():
warn("--prune deletes every POEditor term missing from the local en.json, including its translations.")
warn("Terms from dms-plugins/ are machine-dependent: make sure all official plugins are present before pruning.")
warn("dank-qml-common terms are included from the submodule, so pruning keeps them as long as the submodule is current.")
warn("dms-greeter terms are fetched from POEditor and re-included, so pruning keeps them.")
common_entries = load_common_entries()
common_keys = entry_keys(common_entries)
greeter_entries = load_greeter_entries(api_token, project_id)
greeter_keys = entry_keys(greeter_entries)
extract_strings()
@@ -396,11 +421,13 @@ def main():
common_changed = json.dumps(common_entries, sort_keys=True) != json.dumps(last_common_en, sort_keys=True)
if strings_changed or common_changed or prune:
upload_source_strings(api_token, project_id, combine_entries(current_en, common_entries), prune)
combined = combine_entries(current_en, common_entries)
combined = combine_entries(combined, greeter_entries)
upload_source_strings(api_token, project_id, combined, prune)
else:
info("No changes in source strings")
translations_changed, common_files_changed = download_translations(api_token, project_id, common_keys)
translations_changed, common_files_changed = download_translations(api_token, project_id, common_keys, greeter_keys)
if strings_changed or translations_changed:
subprocess.run(['git', 'add', 'translations/'], cwd=REPO_ROOT)
@@ -103,7 +103,6 @@ fi
targets=(
"${quickshell_dir}/shell.qml"
"${quickshell_dir}/DMSShell.qml"
"${quickshell_dir}/DMSGreeter.qml"
)
qmllint_args=(
-10
View File
@@ -16,7 +16,6 @@ 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: {
@@ -25,7 +24,6 @@ ShellRoot {
Loader {
id: wallpaperLoader
active: !entrypoint.runGreeter
asynchronous: false
sourceComponent: Scope {
@@ -41,7 +39,6 @@ ShellRoot {
Loader {
id: shellCoreLoader
active: !entrypoint.runGreeter
asynchronous: true
source: "ShellCore.qml"
onLoaded: dmsShellLoader.setSource("DMSShell.qml", {
@@ -53,11 +50,4 @@ ShellRoot {
id: dmsShellLoader
asynchronous: true
}
Loader {
id: dmsGreeterLoader
active: entrypoint.runGreeter
asynchronous: false
source: "DMSGreeter.qml"
}
}
@@ -1,3 +0,0 @@
# DMS greeter system user for greetd (declarative; survives ostree /etc/passwd merge).
# Installed as /usr/lib/sysusers.d/dms-greeter.conf
u greeter - "System Greeter" /var/lib/greeter /bin/bash
@@ -1,3 +0,0 @@
# Path Mode User Group Age Argument
d /var/cache/dms-greeter 0750 greeter greeter -
d /var/lib/greeter 0755 greeter greeter -