mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-16 16:15:23 -04:00
feat(Greeter): improved multi-user UI and per-user theme sync
- Introduce multi-account greeter login with per-user theme previews - Add `dms greeter sync --profile` for secondary users with or without sudo - Add Manage greeter group membership from Settings UI → Users Tab
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
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\" {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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user