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:
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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" : "";
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,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: {
|
||||
|
||||
@@ -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=(
|
||||
|
||||
@@ -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 -
|
||||
Reference in New Issue
Block a user