mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-21 18:54:01 -04:00
greeter: own widget list, state-driven sync page, clock typography fix
This commit is contained in:
+1
-1
Submodule dank-qml-common updated: acc16579a1...44058322a4
Generated
+3
-3
@@ -3,11 +3,11 @@
|
||||
"dank-qml-common": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1791213890,
|
||||
"narHash": "sha256-zVQmB7Bv4r7jeoWOOoMsOziqQTO9Owab0xD4pacRXQQ=",
|
||||
"lastModified": 1791221960,
|
||||
"narHash": "sha256-TWWR8Gv0ZBT/+byucBMNKdcjotsymgIBysE2qn2sces=",
|
||||
"owner": "AvengeMedia",
|
||||
"repo": "dank-qml-common",
|
||||
"rev": "acc16579a112fd1f0c65e7ad297962682cd6b22a",
|
||||
"rev": "44058322a4d3261293fa0f8d34933b1256cd5ba0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -294,6 +294,7 @@ Singleton {
|
||||
property var lockScreenWidgetGridSettings: ({})
|
||||
property var desktopWidgetInstancePositions: ({})
|
||||
property var lockScreenAutoPositions: ({})
|
||||
property var greeterAutoPositions: ({})
|
||||
property var islandFreePositions: ({})
|
||||
property var builtInPluginState: ({})
|
||||
property bool greeterSyncPending: false
|
||||
@@ -657,6 +658,14 @@ Singleton {
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setGreeterAutoPositions(screenKey, placement) {
|
||||
if (JSON.stringify(greeterAutoPositions[screenKey] ?? null) === JSON.stringify(placement))
|
||||
return;
|
||||
const updated = Object.assign({}, greeterAutoPositions);
|
||||
updated[screenKey] = placement;
|
||||
greeterAutoPositions = updated;
|
||||
saveSettings();
|
||||
}
|
||||
function setLockScreenAutoPositions(screenKey, placement) {
|
||||
if (JSON.stringify(lockScreenAutoPositions[screenKey] ?? null) === JSON.stringify(placement))
|
||||
return;
|
||||
@@ -666,6 +675,43 @@ Singleton {
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function copyDesktopWidgetInstancePositions(fromId, toId) {
|
||||
const source = desktopWidgetInstancePositions[fromId];
|
||||
if (!source)
|
||||
return;
|
||||
const updated = JSON.parse(JSON.stringify(desktopWidgetInstancePositions));
|
||||
updated[toId] = JSON.parse(JSON.stringify(source));
|
||||
desktopWidgetInstancePositions = updated;
|
||||
saveSettings();
|
||||
}
|
||||
// Detached greeter clocks keep the spot the lock screen picked for them.
|
||||
function pinPublishedLockPosition(fromId, toId, synced) {
|
||||
const updated = JSON.parse(JSON.stringify(desktopWidgetInstancePositions));
|
||||
let pinned = false;
|
||||
for (const screenKey in lockScreenAutoPositions) {
|
||||
const entry = lockScreenAutoPositions[screenKey];
|
||||
const position = entry?.positions?.[fromId];
|
||||
if (!position || !(entry.width > 0) || !(entry.height > 0))
|
||||
continue;
|
||||
const key = synced ? "_synced" : screenKey;
|
||||
if (!updated[toId])
|
||||
updated[toId] = {};
|
||||
updated[toId][key] = Object.assign({}, updated[toId][key] || {}, synced ? {
|
||||
x: position.x / entry.width,
|
||||
y: position.y / entry.height
|
||||
} : {
|
||||
x: position.x,
|
||||
y: position.y
|
||||
});
|
||||
pinned = true;
|
||||
if (synced)
|
||||
break;
|
||||
}
|
||||
if (!pinned)
|
||||
return;
|
||||
desktopWidgetInstancePositions = updated;
|
||||
saveSettings();
|
||||
}
|
||||
function removeDesktopWidgetInstancePositions(instanceId) {
|
||||
if (!(instanceId in desktopWidgetInstancePositions))
|
||||
return;
|
||||
|
||||
@@ -1113,13 +1113,15 @@ Singleton {
|
||||
property var desktopWidgetInstances: Spec.SPEC.desktopWidgetInstances.def
|
||||
property var desktopWidgetGroups: Spec.SPEC.desktopWidgetGroups.def
|
||||
property var lockScreenWidgetInstances: Spec.SPEC.lockScreenWidgetInstances.def
|
||||
readonly property var widgetInstanceListKeys: ["desktopWidgetInstances", "lockScreenWidgetInstances"]
|
||||
property var greeterWidgetInstances: Spec.SPEC.greeterWidgetInstances.def
|
||||
property bool greeterFollowLockScreen: Spec.SPEC.greeterFollowLockScreen.def
|
||||
readonly property var widgetInstanceListKeys: ["desktopWidgetInstances", "lockScreenWidgetInstances", "greeterWidgetInstances"]
|
||||
|
||||
// The greeter still reads these three shared keys, so they follow the lock widgets.
|
||||
// Released greeters still read these three shared keys, so they follow the lock widgets.
|
||||
onLockScreenWidgetInstancesChanged: {
|
||||
const status = lockWidgetInstance("lockStatus");
|
||||
const auth = lockWidgetInstance("lockAuth");
|
||||
const power = lockWidgetInstance("lockPower");
|
||||
const status = widgetInstanceOfType("lockScreenWidgetInstances", "lockStatus");
|
||||
const auth = widgetInstanceOfType("lockScreenWidgetInstances", "lockAuth");
|
||||
const power = widgetInstanceOfType("lockScreenWidgetInstances", "lockPower");
|
||||
const mirror = (key, value) => {
|
||||
if (root[key] !== value)
|
||||
set(key, value);
|
||||
@@ -1127,10 +1129,52 @@ Singleton {
|
||||
mirror("lockScreenShowWeather", !!status && status.enabled !== false && (status.config?.showWeather ?? true));
|
||||
mirror("lockScreenShowProfileImage", !!auth && (auth.config?.showProfileImage ?? true));
|
||||
mirror("lockScreenShowPowerActions", !!power && power.enabled !== false);
|
||||
syncGreeterWidgets();
|
||||
}
|
||||
|
||||
function widgetInstanceOfType(listKey, widgetType) {
|
||||
return (root[listKey] || []).find(inst => inst.widgetType === widgetType) ?? null;
|
||||
}
|
||||
|
||||
function lockWidgetInstance(widgetType) {
|
||||
return (lockScreenWidgetInstances || []).find(inst => inst.widgetType === widgetType) ?? null;
|
||||
return widgetInstanceOfType("lockScreenWidgetInstances", widgetType);
|
||||
}
|
||||
|
||||
function syncGreeterWidgets() {
|
||||
if (!greeterFollowLockScreen)
|
||||
return;
|
||||
const next = Spec.greeterWidgetsFromLock(lockScreenWidgetInstances, greeterWidgetInstances);
|
||||
if (JSON.stringify(next) === JSON.stringify(greeterWidgetInstances))
|
||||
return;
|
||||
set("greeterWidgetInstances", next);
|
||||
}
|
||||
|
||||
function setGreeterFollowLockScreen(follow) {
|
||||
if (follow === greeterFollowLockScreen)
|
||||
return;
|
||||
if (follow) {
|
||||
for (const inst of greeterWidgetInstances || []) {
|
||||
if (inst.id.startsWith("gw_"))
|
||||
SessionData.removeDesktopWidgetInstancePositions(inst.id);
|
||||
}
|
||||
set("greeterFollowLockScreen", true);
|
||||
syncGreeterWidgets();
|
||||
return;
|
||||
}
|
||||
const detached = (greeterWidgetInstances || []).map(inst => {
|
||||
if (inst.widgetType === "greeterSession")
|
||||
return inst;
|
||||
const copy = JSON.parse(JSON.stringify(inst));
|
||||
copy.id = "gw_" + inst.id;
|
||||
SessionData.copyDesktopWidgetInstancePositions(inst.id, copy.id);
|
||||
if (inst.widgetType !== "desktopClock" || inst.config?.autoPosition === false)
|
||||
return copy;
|
||||
copy.config.autoPosition = false;
|
||||
SessionData.pinPublishedLockPosition(inst.id, copy.id, inst.config?.syncPositionAcrossScreens ?? false);
|
||||
return copy;
|
||||
});
|
||||
set("greeterWidgetInstances", detached);
|
||||
set("greeterFollowLockScreen", false);
|
||||
}
|
||||
|
||||
function resetLockScreenWidgets() {
|
||||
@@ -1141,6 +1185,22 @@ Singleton {
|
||||
resetToDefault(["lockScreenWidgetInstances"]);
|
||||
}
|
||||
|
||||
// Following means the lock layout is the greeter layout, so that is what resets.
|
||||
function resetGreeterWidgets() {
|
||||
for (const inst of greeterWidgetInstances || []) {
|
||||
if (!greeterFollowLockScreen || inst.widgetType === "greeterSession")
|
||||
SessionData.removeDesktopWidgetInstancePositions(inst.id);
|
||||
}
|
||||
if (greeterFollowLockScreen) {
|
||||
resetLockScreenWidgets();
|
||||
set("greeterWidgetInstances", Spec.greeterWidgetsFromLock(lockScreenWidgetInstances, []));
|
||||
return;
|
||||
}
|
||||
set("greeterWidgetInstances", Spec.greeterWidgetDefaults().map(inst => inst.widgetType === "greeterSession" ? inst : Object.assign(inst, {
|
||||
id: "gw_" + inst.id
|
||||
})));
|
||||
}
|
||||
|
||||
function getDefaultSystemMonitorConfig() {
|
||||
return {
|
||||
showHeader: true,
|
||||
@@ -1174,10 +1234,16 @@ Singleton {
|
||||
return widgetInstanceListKeys.find(key => (root[key] || []).some(inst => inst.id === instanceId)) ?? "desktopWidgetInstances";
|
||||
}
|
||||
|
||||
readonly property var widgetInstanceIdPrefixes: ({
|
||||
desktopWidgetInstances: "dw_",
|
||||
lockScreenWidgetInstances: "lw_",
|
||||
greeterWidgetInstances: "gw_"
|
||||
})
|
||||
|
||||
function createDesktopWidgetInstance(widgetType, name, config, listKey = "desktopWidgetInstances") {
|
||||
const lockScreen = listKey === "lockScreenWidgetInstances";
|
||||
const lockScreen = listKey !== "desktopWidgetInstances";
|
||||
const instance = {
|
||||
id: (lockScreen ? "lw_" : "dw_") + Date.now() + "_" + Math.random().toString(36).substr(2, 9),
|
||||
id: widgetInstanceIdPrefixes[listKey] + Date.now() + "_" + Math.random().toString(36).substr(2, 9),
|
||||
widgetType: widgetType,
|
||||
name: name || widgetType,
|
||||
enabled: true,
|
||||
@@ -1566,6 +1632,22 @@ Singleton {
|
||||
SessionData.saveSettings();
|
||||
}
|
||||
|
||||
// Older builds flagged keys the linked slot now serves live; drop them so Apply does not nag forever.
|
||||
function pruneGreeterSyncPending() {
|
||||
const baseline = SessionData.greeterSyncBaseline || {};
|
||||
const keys = Object.keys(baseline);
|
||||
const live = keys.filter(key => Spec.SPEC[key]?.onChange === "markGreeterSyncPending");
|
||||
if (live.length === keys.length)
|
||||
return;
|
||||
const pruned = {};
|
||||
for (const key of live)
|
||||
pruned[key] = baseline[key];
|
||||
SessionData.greeterSyncBaseline = pruned;
|
||||
if (live.length === 0)
|
||||
SessionData.greeterSyncPending = false;
|
||||
SessionData.saveSettings();
|
||||
}
|
||||
|
||||
function clearGreeterSyncPending() {
|
||||
SessionData.greeterSyncBaseline = {};
|
||||
SessionData.greeterSyncPending = false;
|
||||
@@ -1778,6 +1860,7 @@ Singleton {
|
||||
function _mergeSessionState() {
|
||||
if (!_hasLoaded || !SessionData._hasLoaded)
|
||||
return;
|
||||
pruneGreeterSyncPending();
|
||||
|
||||
const pluginState = SessionData.builtInPluginState || {};
|
||||
if (Object.keys(pluginState).length > 0) {
|
||||
|
||||
@@ -369,7 +369,7 @@ Singleton {
|
||||
function deferGreeterAutoLoginSyncToPill(details) {
|
||||
toastCategoryDismissed("greeter-autologin-sync");
|
||||
SessionData.set("greeterSyncPending", true);
|
||||
toastRequested(1, I18n.tr("Auto-login change needs a sync"), I18n.tr("Administrator access is required. Use the Sync button in Settings → Greeter to apply.") + (details ? "\n\n" + details : ""), "dms-greeter sync --autologin", "greeter-autologin-sync");
|
||||
toastRequested(1, I18n.tr("Auto-login change needs a sync"), I18n.tr("Administrator access is required. Press Apply changes in Settings → Greeter.") + (details ? "\n\n" + details : ""), "dms-greeter sync --autologin", "greeter-autologin-sync");
|
||||
finishGreeterAutoLoginSync();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,18 @@ function lockWidgetDefaults() {
|
||||
return Shared.lockWidgetDefaults();
|
||||
}
|
||||
|
||||
function greeterWidgetsFromLock(lockInstances, previous) {
|
||||
return Shared.greeterWidgetsFromLock(lockInstances, previous);
|
||||
}
|
||||
|
||||
function greeterSessionDefault() {
|
||||
return Shared.greeterSessionDefault();
|
||||
}
|
||||
|
||||
function greeterWidgetDefaults() {
|
||||
return Shared.greeterWidgetDefaults();
|
||||
}
|
||||
|
||||
var LOCAL_SPEC = {
|
||||
dockConfigs: {
|
||||
def: [DockConfig.create("dock", "Dock")]
|
||||
@@ -645,6 +657,9 @@ var LOCAL_SPEC = {
|
||||
def: false,
|
||||
onChange: "scheduleGreeterAutoLoginSync"
|
||||
},
|
||||
greeterFollowLockScreen: {
|
||||
def: true
|
||||
},
|
||||
greeterPamExternallyManaged: {
|
||||
def: false,
|
||||
onChange: "markGreeterSyncPending"
|
||||
|
||||
@@ -3,150 +3,25 @@ import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import "../../DankCommon/Common/Hct.js" as Hct
|
||||
|
||||
Item {
|
||||
DankClockWidget {
|
||||
id: root
|
||||
|
||||
property real widgetWidth: 280
|
||||
property real widgetHeight: 200
|
||||
|
||||
property string instanceId: ""
|
||||
property var instanceData: null
|
||||
property bool lockScreen: false
|
||||
property var lockHost: null
|
||||
readonly property var cfg: instanceData?.config ?? ({})
|
||||
|
||||
readonly property string face: {
|
||||
switch (cfg.style ?? "analog") {
|
||||
case "analog":
|
||||
case "stacked":
|
||||
case "expressive":
|
||||
case "overlap":
|
||||
return cfg.style;
|
||||
}
|
||||
return "digital";
|
||||
}
|
||||
readonly property bool analog: face === "analog"
|
||||
readonly property bool resizable: true
|
||||
property bool forceSquare: analog
|
||||
readonly property real defaultScale: lockScreen ? 1.75 : 1
|
||||
|
||||
function baseWidth() {
|
||||
switch (face) {
|
||||
case "analog":
|
||||
return 200;
|
||||
case "stacked":
|
||||
return 100;
|
||||
case "overlap":
|
||||
return 160;
|
||||
case "expressive":
|
||||
return 240;
|
||||
}
|
||||
return 160;
|
||||
}
|
||||
|
||||
function baseHeight() {
|
||||
switch (face) {
|
||||
case "analog":
|
||||
case "overlap":
|
||||
return 200;
|
||||
case "stacked":
|
||||
return 160;
|
||||
case "expressive":
|
||||
return 120;
|
||||
}
|
||||
return 70;
|
||||
}
|
||||
|
||||
property real defaultWidth: baseWidth() * defaultScale
|
||||
property real defaultHeight: baseHeight() * defaultScale
|
||||
property real minWidth: {
|
||||
switch (face) {
|
||||
case "analog":
|
||||
return 120;
|
||||
case "stacked":
|
||||
return 70;
|
||||
case "overlap":
|
||||
return 80;
|
||||
}
|
||||
return 100;
|
||||
}
|
||||
property real minHeight: {
|
||||
switch (face) {
|
||||
case "analog":
|
||||
return 120;
|
||||
case "stacked":
|
||||
case "overlap":
|
||||
return 100;
|
||||
}
|
||||
return 45;
|
||||
}
|
||||
|
||||
cfg: instanceData?.config ?? ({})
|
||||
enabled: instanceData?.enabled ?? true
|
||||
readonly property real transparency: cfg.transparency ?? (lockScreen ? 0 : 0.8)
|
||||
readonly property string colorMode: cfg.colorMode ?? (lockScreen ? "default" : "primary")
|
||||
readonly property color customColor: cfg.customColor ?? "#ffffff"
|
||||
readonly property bool showDate: cfg.showDate ?? !lockScreen
|
||||
readonly property bool twoTone: cfg.twoTone ?? lockScreen
|
||||
readonly property bool showAnalogNumbers: cfg.showAnalogNumbers ?? false
|
||||
readonly property bool showAnalogSeconds: cfg.showAnalogSeconds ?? true
|
||||
readonly property bool showDigitalSeconds: cfg.showDigitalSeconds ?? false
|
||||
readonly property bool italic: face === "digital" && (cfg.italic ?? false)
|
||||
readonly property string fontFamily: cfg.fontFamily || (lockScreen ? SettingsData.lockScreenFontFamily : "")
|
||||
readonly property int weight: cfg.weight ?? 0
|
||||
readonly property int digitWeight: weight > 0 ? weight : Theme.fontWeightMedium
|
||||
|
||||
readonly property color accentColor: {
|
||||
if (lockHost)
|
||||
return lockHost.contentColor(colorMode, customColor);
|
||||
switch (colorMode) {
|
||||
case "secondary":
|
||||
return Theme.secondary;
|
||||
case "custom":
|
||||
return customColor;
|
||||
}
|
||||
return Theme.primary;
|
||||
}
|
||||
readonly property color minutesColor: twoTone ? Theme.primary : accentColor
|
||||
readonly property color dimColor: Theme.withAlpha(accentColor, 0.65)
|
||||
readonly property color backgroundColor: Theme.withAlpha(Theme.hostSurface, transparency)
|
||||
readonly property bool themedOverlap: lockScreen && colorMode === "default"
|
||||
readonly property var primaryHct: Hct.toHct(Theme.primary)
|
||||
readonly property color overlapFaceColor: {
|
||||
if (!themedOverlap)
|
||||
return accentColor;
|
||||
return Theme.isLightMode ? Hct.fromHct(primaryHct.hue, primaryHct.chroma, 65) : Theme.accentOnPrimaryContainer;
|
||||
}
|
||||
readonly property color overlapShadowColor: {
|
||||
if (themedOverlap)
|
||||
return Theme.isLightMode ? Hct.fromHct(primaryHct.hue, primaryHct.chroma, 30) : Theme.primaryContainer;
|
||||
const source = Hct.toHct(accentColor);
|
||||
return Hct.fromHct(source.hue, source.chroma, source.tone > 50 ? 30 : 80);
|
||||
}
|
||||
readonly property var contrastColors: {
|
||||
switch (face) {
|
||||
case "overlap":
|
||||
return [overlapFaceColor, overlapShadowColor];
|
||||
case "digital":
|
||||
case "stacked":
|
||||
case "expressive":
|
||||
return [accentColor, minutesColor];
|
||||
}
|
||||
return [accentColor];
|
||||
}
|
||||
|
||||
readonly property bool needsSeconds: analog ? showAnalogSeconds : showDigitalSeconds
|
||||
readonly property string hoursText: {
|
||||
const hours = systemClock.date?.getHours() ?? 0;
|
||||
const display = SettingsData.use24HourClock ? hours : (hours % 12 || 12);
|
||||
return SettingsData.use24HourClock || SettingsData.padHours12Hour ? String(display).padStart(2, "0") : String(display);
|
||||
}
|
||||
readonly property string minutesText: String(systemClock.date?.getMinutes() ?? 0).padStart(2, "0")
|
||||
readonly property string secondsText: String(systemClock.date?.getSeconds() ?? 0).padStart(2, "0")
|
||||
readonly property string meridiem: (systemClock.date?.getHours() ?? 0) >= 12 ? "PM" : "AM"
|
||||
readonly property string dateFormat: lockScreen ? SettingsData.lockDateFormat : SettingsData.clockDateFormat
|
||||
readonly property string formattedDate: systemClock.date?.toLocaleDateString(I18n.locale(), dateFormat || "ddd, MMM d") ?? ""
|
||||
now: systemClock.date
|
||||
locale: I18n.locale()
|
||||
use24HourClock: SettingsData.use24HourClock
|
||||
padHours12Hour: SettingsData.padHours12Hour
|
||||
dateFormat: lockScreen ? SettingsData.lockDateFormat : SettingsData.clockDateFormat
|
||||
fallbackFontFamily: SettingsData.lockScreenFontFamily
|
||||
accentColor: lockHost ? lockHost.contentColor(colorMode, customColor) : themeAccent
|
||||
|
||||
SystemClock {
|
||||
id: systemClock
|
||||
@@ -161,277 +36,4 @@ Item {
|
||||
systemClock.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
component DigitText: StyledText {
|
||||
font.weight: root.digitWeight
|
||||
font.italic: root.italic
|
||||
font.family: root.fontFamily !== "" ? root.fontFamily : resolvedFontFamily
|
||||
font.features: ({
|
||||
"tnum": 1
|
||||
})
|
||||
color: root.accentColor
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: root.backgroundColor
|
||||
visible: !root.analog
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
anchors.margins: root.analog ? 0 : Theme.spacingM
|
||||
sourceComponent: {
|
||||
switch (root.face) {
|
||||
case "analog":
|
||||
return analogClock;
|
||||
case "stacked":
|
||||
return stackedClock;
|
||||
case "expressive":
|
||||
return expressiveClock;
|
||||
case "overlap":
|
||||
return overlapClock;
|
||||
}
|
||||
return digitalClock;
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: analogClock
|
||||
|
||||
DankAnalogClock {
|
||||
hours: systemClock.date?.getHours() ?? 0
|
||||
minutes: systemClock.date?.getMinutes() ?? 0
|
||||
seconds: systemClock.date?.getSeconds() ?? 0
|
||||
showSeconds: root.showAnalogSeconds
|
||||
showNumbers: root.showAnalogNumbers
|
||||
dateText: root.showDate ? root.formattedDate : ""
|
||||
color: root.accentColor
|
||||
backgroundColor: root.backgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: expressiveClock
|
||||
|
||||
DankClockFace {
|
||||
readonly property bool tallBox: height > width * 0.6
|
||||
|
||||
hours: root.hoursText
|
||||
minutes: root.minutesText
|
||||
seconds: root.showDigitalSeconds && !tallBox ? root.secondsText : ""
|
||||
dateText: root.showDate ? root.formattedDate : ""
|
||||
stacked: tallBox
|
||||
color: root.accentColor
|
||||
minutesColor: root.minutesColor
|
||||
supportingColor: root.dimColor
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: overlapClock
|
||||
|
||||
Item {
|
||||
DankOverlapClockFace {
|
||||
anchors.centerIn: parent
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
hours: root.hoursText
|
||||
minutes: root.minutesText
|
||||
color: root.overlapFaceColor
|
||||
shadowColor: root.overlapShadowColor
|
||||
fontFamily: root.fontFamily
|
||||
weight: root.weight > 0 ? root.weight : 1000
|
||||
scale: implicitWidth > 0 && implicitHeight > 0 ? Math.min(parent.width / implicitWidth, parent.height / implicitHeight) : 1
|
||||
renderScale: scale
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: digitalClock
|
||||
|
||||
Item {
|
||||
id: digitalRoot
|
||||
|
||||
readonly property bool hasAmPm: !SettingsData.use24HourClock
|
||||
readonly property real verticalScale: root.showDate && hasAmPm ? 0.55 : (root.showDate || hasAmPm ? 0.65 : 0.8)
|
||||
readonly property real baseSize: Math.min(height * verticalScale, width * 0.22)
|
||||
readonly property real digitWidth: baseSize * 0.62
|
||||
readonly property real smallSize: baseSize * 0.35
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: 0
|
||||
|
||||
DigitText {
|
||||
visible: root.showDate
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: root.formattedDate
|
||||
font.pixelSize: digitalRoot.smallSize
|
||||
color: root.dimColor
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: 0
|
||||
|
||||
Repeater {
|
||||
model: root.hoursText.split("")
|
||||
|
||||
DigitText {
|
||||
required property string modelData
|
||||
width: digitalRoot.digitWidth
|
||||
text: modelData
|
||||
font.pixelSize: digitalRoot.baseSize
|
||||
}
|
||||
}
|
||||
|
||||
DigitText {
|
||||
text: ":"
|
||||
font.pixelSize: digitalRoot.baseSize
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.minutesText.split("")
|
||||
|
||||
DigitText {
|
||||
required property string modelData
|
||||
width: digitalRoot.digitWidth
|
||||
text: modelData
|
||||
font.pixelSize: digitalRoot.baseSize
|
||||
color: root.minutesColor
|
||||
}
|
||||
}
|
||||
|
||||
DigitText {
|
||||
visible: root.showDigitalSeconds
|
||||
text: ":"
|
||||
font.pixelSize: digitalRoot.baseSize
|
||||
color: root.dimColor
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.showDigitalSeconds ? root.secondsText.split("") : []
|
||||
|
||||
DigitText {
|
||||
required property string modelData
|
||||
width: digitalRoot.digitWidth
|
||||
text: modelData
|
||||
font.pixelSize: digitalRoot.baseSize
|
||||
color: root.dimColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DigitText {
|
||||
visible: digitalRoot.hasAmPm
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: root.meridiem
|
||||
font.pixelSize: digitalRoot.smallSize
|
||||
color: root.dimColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: stackedClock
|
||||
|
||||
Item {
|
||||
id: stackedRoot
|
||||
|
||||
readonly property bool hasAmPm: !SettingsData.use24HourClock
|
||||
readonly property real extraContent: (root.showDigitalSeconds ? 0.12 : 0) + (root.showDate ? 0.08 : 0) + (hasAmPm ? 0.08 : 0)
|
||||
readonly property real baseSize: height * (0.42 - extraContent * 0.5)
|
||||
readonly property real digitWidth: baseSize * 0.58
|
||||
readonly property real smallSize: baseSize * 0.5
|
||||
readonly property real rowSpacing: -baseSize * 0.17
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: 0
|
||||
|
||||
Column {
|
||||
spacing: stackedRoot.rowSpacing
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Row {
|
||||
spacing: 0
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Repeater {
|
||||
model: root.hoursText.padStart(2, "0").split("")
|
||||
|
||||
DigitText {
|
||||
required property string modelData
|
||||
width: stackedRoot.digitWidth
|
||||
text: modelData
|
||||
font.pixelSize: stackedRoot.baseSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 0
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Repeater {
|
||||
model: root.minutesText.split("")
|
||||
|
||||
DigitText {
|
||||
required property string modelData
|
||||
width: stackedRoot.digitWidth
|
||||
text: modelData
|
||||
font.pixelSize: stackedRoot.baseSize
|
||||
color: root.minutesColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
visible: root.showDigitalSeconds
|
||||
spacing: 0
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Repeater {
|
||||
model: root.showDigitalSeconds ? root.secondsText.split("") : []
|
||||
|
||||
DigitText {
|
||||
required property string modelData
|
||||
width: stackedRoot.smallSize * 0.58
|
||||
text: modelData
|
||||
font.pixelSize: stackedRoot.smallSize
|
||||
color: root.dimColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 1
|
||||
height: stackedRoot.baseSize * 0.1
|
||||
visible: root.showDate
|
||||
}
|
||||
|
||||
DigitText {
|
||||
visible: root.showDate
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: systemClock.date?.toLocaleDateString(I18n.locale(), "MMM dd") ?? ""
|
||||
font.pixelSize: stackedRoot.smallSize * 0.7
|
||||
color: root.dimColor
|
||||
}
|
||||
|
||||
DigitText {
|
||||
visible: stackedRoot.hasAmPm
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: root.meridiem
|
||||
font.pixelSize: stackedRoot.smallSize * 0.7
|
||||
color: root.dimColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,6 +242,12 @@ Scope {
|
||||
demoWindow.showDemo();
|
||||
}
|
||||
|
||||
function onGreeterEditorRequested() {
|
||||
if (shouldLock)
|
||||
return;
|
||||
demoWindow.showDemo(true);
|
||||
}
|
||||
|
||||
function onLoginctlStateChanged() {
|
||||
if (SessionService.active && pendingLock) {
|
||||
pendingLock = false;
|
||||
|
||||
@@ -21,6 +21,7 @@ FocusScope {
|
||||
inputRevealRequested(revealed);
|
||||
}
|
||||
property bool demoMode: false
|
||||
property bool greeterPreview: false
|
||||
property var pam: demoPam
|
||||
property string screenName: ""
|
||||
property bool unlocking: false
|
||||
@@ -155,6 +156,7 @@ FocusScope {
|
||||
focus: true
|
||||
screenName: root.screenName
|
||||
lockScreen: true
|
||||
greeter: root.greeterPreview
|
||||
lockHost: root
|
||||
editMode: root.demoMode
|
||||
bottomInset: editorLoader.item?.fabReserved ?? 0
|
||||
|
||||
@@ -10,9 +10,11 @@ Scope {
|
||||
readonly property var log: Log.scoped("LockScreenDemo")
|
||||
|
||||
property bool demoActive: false
|
||||
property bool greeterPreview: false
|
||||
|
||||
function showDemo(): void {
|
||||
log.debug("Showing lock screen demo");
|
||||
function showDemo(greeter = false): void {
|
||||
log.debug("Showing lock screen demo", greeter ? "(greeter)" : "");
|
||||
greeterPreview = greeter;
|
||||
demoActive = true;
|
||||
}
|
||||
|
||||
@@ -49,6 +51,7 @@ Scope {
|
||||
LockScreenContent {
|
||||
anchors.fill: parent
|
||||
demoMode: true
|
||||
greeterPreview: root.greeterPreview
|
||||
screenName: demoWindow.screen?.name ?? ""
|
||||
onUnlockRequested: root.hideDemo()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.DankCommon.Session
|
||||
|
||||
// Settings preview of the greeter session picker. The greeter renders the real dropdown.
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var instanceData: null
|
||||
property var lockHost: null
|
||||
readonly property bool resizable: false
|
||||
readonly property real minWidth: implicitWidth
|
||||
readonly property real minHeight: implicitHeight
|
||||
readonly property string sessionName: CompositorService.displayName || I18n.tr("Session", "greeter session picker widget")
|
||||
|
||||
implicitWidth: Math.max(Theme.fieldDefaultWidth, sessionMetrics.width + Theme.buttonHeightM + Theme.spacingXL)
|
||||
implicitHeight: LockMetrics.fieldHeight
|
||||
|
||||
StyledTextMetrics {
|
||||
id: sessionMetrics
|
||||
text: root.sessionName
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
anchors.fill: parent
|
||||
text: ""
|
||||
description: ""
|
||||
backgroundColor: Theme.cardSurface
|
||||
hoverBackgroundColor: Theme.blend(Theme.cardSurface, Theme.onSurface, Theme.stateLayerHover)
|
||||
normalBorderColor: Theme.outlineMedium
|
||||
currentValue: root.sessionName
|
||||
options: [root.sessionName]
|
||||
popupWidthOffset: 0
|
||||
openUpwards: true
|
||||
alignPopupRight: true
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ Item {
|
||||
anchors.right: parent.right
|
||||
anchors.margins: root.pad
|
||||
interactive: !(root.lockHost?.demoMode ?? true)
|
||||
showMediaPlayer: root.cfg.showMediaPlayer ?? true
|
||||
showMediaPlayer: (root.cfg.showMediaPlayer ?? true) && !(root.lockHost?.greeterPreview ?? false)
|
||||
showWeather: root.cfg.showWeather ?? true
|
||||
useFahrenheit: SettingsData.useFahrenheit
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ Loader {
|
||||
return lockStatusComponent;
|
||||
case "lockPower":
|
||||
return lockPowerComponent;
|
||||
case "greeterSession":
|
||||
return greeterSessionComponent;
|
||||
}
|
||||
return PluginService.pluginDesktopComponents[pluginId] ?? null;
|
||||
}
|
||||
@@ -72,6 +74,10 @@ Loader {
|
||||
LockPowerWidget {}
|
||||
}
|
||||
|
||||
property Component greeterSessionComponent: Component {
|
||||
GreeterSessionWidget {}
|
||||
}
|
||||
|
||||
onInstanceDataChanged: {
|
||||
if (!item || item.instanceData === undefined)
|
||||
return;
|
||||
|
||||
@@ -14,7 +14,7 @@ Item {
|
||||
readonly property string instanceId: instanceData?.id ?? ""
|
||||
readonly property string widgetType: instanceData?.widgetType ?? ""
|
||||
readonly property bool lockScreen: hostLayer.lockScreen
|
||||
readonly property bool removable: widgetType !== "lockAuth"
|
||||
readonly property bool removable: widgetType !== "lockAuth" && widgetType !== "greeterSession"
|
||||
readonly property bool selected: hostLayer.selectedInstanceId === instanceId
|
||||
readonly property bool interacting: editChrome.item?.interacting ?? false
|
||||
readonly property var contrastColors: content.item?.contrastColors ?? []
|
||||
|
||||
@@ -12,13 +12,14 @@ FocusScope {
|
||||
|
||||
required property string screenName
|
||||
property bool lockScreen: false
|
||||
property bool greeter: false
|
||||
property var lockHost: null
|
||||
property bool editMode: false
|
||||
property string selectedInstanceId: ""
|
||||
|
||||
readonly property var screen: Quickshell.screens.find(s => s.name === screenName) ?? null
|
||||
readonly property string listKey: lockScreen ? "lockScreenWidgetInstances" : "desktopWidgetInstances"
|
||||
readonly property var instances: (lockScreen ? SettingsData.lockScreenWidgetInstances : SettingsData.desktopWidgetInstances) || []
|
||||
readonly property string listKey: greeter ? "greeterWidgetInstances" : (lockScreen ? "lockScreenWidgetInstances" : "desktopWidgetInstances")
|
||||
readonly property var instances: (greeter ? SettingsData.greeterWidgetInstances : (lockScreen ? SettingsData.lockScreenWidgetInstances : SettingsData.desktopWidgetInstances)) || []
|
||||
readonly property string screenKey: SettingsData.getScreenDisplayName(screen)
|
||||
property var _gridSettingsTrigger: lockScreen ? SessionData.lockScreenWidgetGridSettings : SessionData.desktopWidgetGridSettings
|
||||
readonly property int gridSize: {
|
||||
@@ -133,6 +134,17 @@ FocusScope {
|
||||
return LockPlacementService.layoutFor(screenName, LockPlacementService.sampleFor(screenName, backgroundKey), placementKey, placeClocks);
|
||||
}
|
||||
|
||||
// The greeter list never renders outside the editor, so the editor publishes its auto placement.
|
||||
onAutoPositionsChanged: {
|
||||
if (!greeter || !editMode || !completed || interactingItem || width <= 0 || height <= 0)
|
||||
return;
|
||||
SessionData.setGreeterAutoPositions(screenKey, {
|
||||
width: width,
|
||||
height: height,
|
||||
positions: autoPositions
|
||||
});
|
||||
}
|
||||
|
||||
function refreshPlacement() {
|
||||
if (!lockScreen || !completed || editMode || width <= 0 || height <= 0)
|
||||
return;
|
||||
@@ -204,6 +216,11 @@ FocusScope {
|
||||
x: Theme.spacingXL,
|
||||
y: height - Theme.spacingXL - item.height
|
||||
};
|
||||
case "greeterSession":
|
||||
return {
|
||||
x: width - Theme.spacingXL - item.width,
|
||||
y: height - Theme.spacingXL - item.height
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -14,12 +14,14 @@ FocusScope {
|
||||
|
||||
required property WidgetEditLayer editLayer
|
||||
readonly property bool lockScreen: editLayer.lockScreen
|
||||
readonly property bool greeter: editLayer.greeter
|
||||
property bool libraryOpen: false
|
||||
readonly property real fabReserved: fabBar.reservedHeight
|
||||
readonly property var libraryWidgets: DesktopWidgetRegistry.registeredWidgetsList.filter(widget => {
|
||||
readonly property var singletonTypes: ["lockAuth", "greeterSession"]
|
||||
readonly property var libraryWidgets: DesktopWidgetRegistry.getListWidgets(editLayer.listKey).filter(widget => {
|
||||
if (!lockScreen)
|
||||
return !widget.lockOnly;
|
||||
return widget.id !== "lockAuth" || !SettingsData.lockWidgetInstance("lockAuth");
|
||||
return true;
|
||||
return !singletonTypes.includes(widget.id) || !SettingsData.widgetInstanceOfType(editLayer.listKey, widget.id);
|
||||
}).map(widget => ({
|
||||
id: widget.id,
|
||||
text: widget.name,
|
||||
@@ -27,6 +29,14 @@ FocusScope {
|
||||
description: widget.description
|
||||
}))
|
||||
|
||||
readonly property string surfaceLabel: {
|
||||
if (greeter)
|
||||
return SettingsData.greeterFollowLockScreen ? I18n.tr("Editing the login screen, shared with the lock screen", "widget editor banner") : I18n.tr("Editing the login screen", "widget editor banner");
|
||||
if (!lockScreen || !GreeterService.available)
|
||||
return "";
|
||||
return SettingsData.greeterFollowLockScreen && GreeterService.slotState !== "" ? I18n.tr("Editing the lock screen, the login screen follows it", "widget editor banner") : I18n.tr("Editing the lock screen", "widget editor banner");
|
||||
}
|
||||
|
||||
signal finished
|
||||
|
||||
function closeLibrary() {
|
||||
@@ -46,6 +56,10 @@ FocusScope {
|
||||
}
|
||||
|
||||
function resetLayout() {
|
||||
if (greeter) {
|
||||
SettingsData.resetGreeterWidgets();
|
||||
return;
|
||||
}
|
||||
if (lockScreen) {
|
||||
SettingsData.resetLockScreenWidgets();
|
||||
return;
|
||||
@@ -119,7 +133,42 @@ FocusScope {
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: root.surfaceLabel !== "" && fabBar.shown
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: fabBar.reservedHeight + Theme.spacingL + (gridHint.visible ? gridHint.height + Theme.spacingS : 0)
|
||||
width: surfaceRow.implicitWidth + Theme.spacingL * 2
|
||||
height: Theme.buttonHeightM
|
||||
radius: Theme.fullRadius(width, height)
|
||||
color: Theme.readableSurface
|
||||
border.width: Theme.layerOutlineWidth
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
Row {
|
||||
id: surfaceRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: root.greeter ? "login" : "lock"
|
||||
size: Theme.iconSizeMedium
|
||||
color: Theme.primary
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.surfaceLabel
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Theme.fontWeightMedium
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DesktopWidgetGridHint {
|
||||
id: gridHint
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: fabBar.reservedHeight + Theme.spacingL
|
||||
|
||||
@@ -23,7 +23,7 @@ WidgetPickerWindow {
|
||||
|
||||
objectName: "desktopWidgetBrowser"
|
||||
title: I18n.tr("Add Desktop Widget")
|
||||
widgets: (DesktopWidgetRegistry.registeredWidgetsList || []).filter(widget => !widget.lockOnly)
|
||||
widgets: DesktopWidgetRegistry.getListWidgets(listKey).filter(widget => listKey === "desktopWidgetInstances" ? true : (widget.id !== "lockAuth" && widget.id !== "greeterSession"))
|
||||
featuredFirst: true
|
||||
showEmptyState: true
|
||||
widgetDelegate: tileDelegate
|
||||
|
||||
@@ -41,6 +41,13 @@ DesktopWidgetInstanceSettings {
|
||||
onSliderValueChanged: newValue => root.updateConfig("weight", newValue)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
visible: root.style === "expressive"
|
||||
text: I18n.tr("Rounded")
|
||||
checked: root.cfg.rounded ?? true
|
||||
onToggled: checked => root.updateConfig("rounded", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
visible: root.style === "digital"
|
||||
text: I18n.tr("Italic", "clock font style toggle")
|
||||
|
||||
@@ -10,7 +10,9 @@ Column {
|
||||
property bool showAppearance: true
|
||||
property bool showPlacement: true
|
||||
readonly property var cfg: instanceData?.config ?? {}
|
||||
readonly property bool lockScreenInstance: SettingsData.widgetInstanceListKey(instanceId) === "lockScreenWidgetInstances"
|
||||
readonly property string listKey: SettingsData.widgetInstanceListKey(instanceId)
|
||||
readonly property bool lockScreenInstance: listKey !== "desktopWidgetInstances"
|
||||
readonly property bool greeterInstance: listKey === "greeterWidgetInstances"
|
||||
default property alias rows: optionsCard.content
|
||||
|
||||
function updateConfig(key, value) {
|
||||
|
||||
@@ -27,6 +27,8 @@ Loader {
|
||||
return lockDateSettings;
|
||||
case "lockPower":
|
||||
return lockPowerSettings;
|
||||
case "greeterSession":
|
||||
return null;
|
||||
default:
|
||||
return pluginSettings;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,9 @@ Item {
|
||||
property var parentModal: null
|
||||
|
||||
readonly property string instanceId: SettingsUiState.selectedDesktopWidgetId
|
||||
readonly property bool lockScreenInstance: SettingsData.widgetInstanceListKey(instanceId) === "lockScreenWidgetInstances"
|
||||
readonly property string listKey: SettingsData.widgetInstanceListKey(instanceId)
|
||||
readonly property bool lockScreenInstance: listKey !== "desktopWidgetInstances"
|
||||
readonly property bool greeterInstance: listKey === "greeterWidgetInstances"
|
||||
readonly property var instanceData: SettingsData.getDesktopWidgetInstance(instanceId)
|
||||
readonly property string widgetType: instanceData?.widgetType ?? ""
|
||||
readonly property var widgetDef: DesktopWidgetRegistry.getWidget(widgetType)
|
||||
|
||||
@@ -20,7 +20,7 @@ Item {
|
||||
|
||||
switch (SettingsData.greeterFingerprintReason) {
|
||||
case "ready":
|
||||
return I18n.tr("Applies on the next greeter sync", "greeter auth setting description");
|
||||
return I18n.tr("Press Apply changes to update the login screen", "greeter auth setting description");
|
||||
case "missing_enrollment":
|
||||
return I18n.tr("Fingerprint reader detected, but no prints are enrolled yet. You can enable this now and run Sync later.", "greeter fingerprint login setting");
|
||||
case "missing_reader":
|
||||
@@ -40,7 +40,7 @@ Item {
|
||||
|
||||
switch (SettingsData.greeterU2fReason) {
|
||||
case "ready":
|
||||
return I18n.tr("Applies on the next greeter sync", "greeter auth setting description");
|
||||
return I18n.tr("Press Apply changes to update the login screen", "greeter auth setting description");
|
||||
case "missing_key_registration":
|
||||
return I18n.tr("Security-key support was detected, but no registered key was found yet. You can enable this now and register one later.", "security key setting status");
|
||||
case "missing_pam_support":
|
||||
|
||||
@@ -12,15 +12,16 @@ SettingsFabBar {
|
||||
shown: SessionData.greeterSyncPending && GreeterService.binaryExists
|
||||
|
||||
DankFab {
|
||||
iconName: "close"
|
||||
text: I18n.tr("Revert")
|
||||
iconName: "undo"
|
||||
colorRole: "secondaryContainer"
|
||||
Accessible.name: I18n.tr("Dismiss")
|
||||
enabled: !GreeterService.syncing
|
||||
onClicked: SettingsData.revertGreeterSyncPending()
|
||||
}
|
||||
|
||||
DankFab {
|
||||
text: GreeterService.syncing ? I18n.tr("Syncing...", "greeter settings status while sync is running") : I18n.tr("Sync to apply")
|
||||
iconName: "sync"
|
||||
text: GreeterService.syncing ? I18n.tr("Syncing...", "greeter settings status while sync is running") : I18n.tr("Apply changes")
|
||||
iconName: "check"
|
||||
colorRole: "primary"
|
||||
busy: GreeterService.syncing
|
||||
enabled: !GreeterService.syncing && !root.blocked
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Modals.Common
|
||||
@@ -20,6 +21,38 @@ Item {
|
||||
id: greeterActionConfirm
|
||||
}
|
||||
|
||||
ConfirmModal {
|
||||
id: unlinkConfirm
|
||||
}
|
||||
|
||||
ConfirmModal {
|
||||
id: unlinkFinalConfirm
|
||||
}
|
||||
|
||||
function promptUnlink() {
|
||||
unlinkConfirm.showWithOptions({
|
||||
"title": I18n.tr("Unlink login screen", "greeter advanced action"),
|
||||
"message": I18n.tr("The login screen stops following your DMS theme, wallpaper, widgets and settings. greetd, authentication and other users are not affected.", "greeter unlink confirmation"),
|
||||
"confirmText": I18n.tr("Unlink", "verb, greeter unlink button"),
|
||||
"cancelText": I18n.tr("Cancel"),
|
||||
"confirmColor": Theme.error,
|
||||
"onConfirm": () => Qt.callLater(root.promptUnlinkFinal),
|
||||
"onCancel": () => {}
|
||||
});
|
||||
}
|
||||
|
||||
function promptUnlinkFinal() {
|
||||
unlinkFinalConfirm.showWithOptions({
|
||||
"title": I18n.tr("Unlink now?", "greeter unlink second confirmation"),
|
||||
"message": I18n.tr("Your greeter slot and any cache links to your home are removed. Administrator access may be required. Set up links them again.", "greeter unlink second confirmation"),
|
||||
"confirmText": I18n.tr("Unlink", "verb, greeter unlink button"),
|
||||
"cancelText": I18n.tr("Cancel"),
|
||||
"confirmColor": Theme.error,
|
||||
"onConfirm": () => GreeterService.unlink(),
|
||||
"onCancel": () => {}
|
||||
});
|
||||
}
|
||||
|
||||
property string greeterStatusText: ""
|
||||
property bool greeterStatusRunning: false
|
||||
readonly property bool greeterSyncRunning: GreeterService.syncing
|
||||
@@ -28,31 +61,93 @@ Item {
|
||||
property string greeterStatusStdout: ""
|
||||
property string greeterStatusStderr: ""
|
||||
readonly property bool greeterBinaryExists: GreeterService.binaryExists
|
||||
property bool greeterEnabled: false
|
||||
readonly property bool greeterEnabled: GreeterService.enabled
|
||||
property bool embeddedGreeterConfigured: false
|
||||
readonly property bool embeddedGreeterOnly: embeddedGreeterConfigured && !greeterBinaryExists
|
||||
readonly property string greeterAction: greeterBinaryExists && !greeterEnabled ? "activate" : ""
|
||||
readonly property bool greeterActionAvailable: greeterAction !== ""
|
||||
readonly property bool busy: greeterSyncRunning || greeterInstallActionRunning
|
||||
|
||||
readonly property string greeterActionLabel: greeterAction === "activate" ? I18n.tr("Activate") : ""
|
||||
readonly property string greeterActionIcon: greeterAction === "activate" ? "login" : ""
|
||||
readonly property var greeterActionCommand: greeterAction === "activate" ? ["dms-greeter", "enable", "--terminal"] : []
|
||||
readonly property string greeterStatusOutput: {
|
||||
if (greeterStatusRunning)
|
||||
return I18n.tr("Checking...", "greeter status loading");
|
||||
if (greeterStatusText !== "")
|
||||
return greeterStatusText;
|
||||
if (embeddedGreeterOnly)
|
||||
readonly property string greeterState: {
|
||||
if (!greeterBinaryExists)
|
||||
return embeddedGreeterOnly ? "embedded" : "missing";
|
||||
if (!greeterEnabled)
|
||||
return "inactive";
|
||||
switch (GreeterService.slotState) {
|
||||
case "live":
|
||||
return "linked";
|
||||
case "snapshot":
|
||||
return "snapshot";
|
||||
}
|
||||
return "unlinked";
|
||||
}
|
||||
readonly property bool greeterStateWarn: greeterState !== "linked"
|
||||
readonly property string greeterStateIcon: {
|
||||
switch (greeterState) {
|
||||
case "linked":
|
||||
return "link";
|
||||
case "snapshot":
|
||||
return "history";
|
||||
case "inactive":
|
||||
return "login";
|
||||
case "unlinked":
|
||||
return "link_off";
|
||||
}
|
||||
return "info";
|
||||
}
|
||||
readonly property string greeterStateTitle: {
|
||||
switch (greeterState) {
|
||||
case "linked":
|
||||
return I18n.tr("Linked", "greeter status, login screen follows DMS settings live");
|
||||
case "snapshot":
|
||||
return I18n.tr("Linked as a snapshot", "greeter status, login screen reads a copy of DMS settings");
|
||||
case "inactive":
|
||||
return I18n.tr("Not active", "greeter status, greetd does not run the DMS greeter");
|
||||
case "unlinked":
|
||||
return I18n.tr("Not linked", "greeter status, no greeter slot for this user");
|
||||
case "embedded":
|
||||
return I18n.tr("Bundled greeter active", "greeter status");
|
||||
}
|
||||
return I18n.tr("Not installed", "greeter status");
|
||||
}
|
||||
readonly property string greeterStateSubtitle: {
|
||||
switch (greeterState) {
|
||||
case "linked":
|
||||
return I18n.tr("The login screen follows your theme, wallpaper, widgets and behavior live. Only authentication changes need applying.", "greeter status");
|
||||
case "snapshot":
|
||||
return I18n.tr("This system cannot link settings live. Sync again after changing them.", "greeter status");
|
||||
case "inactive":
|
||||
return I18n.tr("greetd is not using the DMS greeter yet.", "greeter status");
|
||||
case "unlinked":
|
||||
return GreeterService.profileSyncSufficient ? I18n.tr("Link your DMS settings so the login screen follows them.", "greeter status") : I18n.tr("Link your DMS settings so the login screen follows them. Administrator access is required.", "greeter status");
|
||||
case "embedded":
|
||||
return I18n.tr("The greeter bundled with DMS is active (archinstall setup). It keeps working as is, but syncing theme and settings needs the standalone greeter. Install greetd-dms-greeter-bin from the AUR, then run Sync to migrate the login screen.", "embedded greeter status");
|
||||
if (!greeterBinaryExists && greeterEnabled)
|
||||
return I18n.tr("dms-greeter is not installed. Install the dms-greeter package to manage the greeter.", "greeter status placeholder");
|
||||
}
|
||||
return I18n.tr("dms-greeter is not installed. Install the dms-greeter package to manage the greeter.", "greeter status placeholder");
|
||||
}
|
||||
readonly property string greeterStateAction: {
|
||||
switch (greeterState) {
|
||||
case "inactive":
|
||||
return I18n.tr("Activate");
|
||||
case "unlinked":
|
||||
return I18n.tr("Set up", "greeter setup button, links DMS settings to the login screen");
|
||||
case "snapshot":
|
||||
return I18n.tr("Sync", "verb, button that copies settings to the login greeter");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function runStateAction() {
|
||||
if (greeterState === "inactive") {
|
||||
promptGreeterActionConfirm();
|
||||
return;
|
||||
}
|
||||
GreeterService.link();
|
||||
}
|
||||
|
||||
readonly property string greeterStatusOutput: greeterStatusRunning ? I18n.tr("Checking...", "greeter status loading") : greeterStatusText
|
||||
|
||||
onGreeterSyncStatusChanged: greeterStatusText = greeterSyncStatus
|
||||
|
||||
function checkGreeterInstallState() {
|
||||
greetdEnabledCheckProcess.running = true;
|
||||
GreeterService.refresh();
|
||||
embeddedGreeterCheckProcess.running = true;
|
||||
}
|
||||
@@ -66,15 +161,12 @@ Item {
|
||||
}
|
||||
|
||||
function runGreeterInstallAction() {
|
||||
greeterStatusText = I18n.tr("Opening terminal: ") + root.greeterActionLabel + "...";
|
||||
greeterStatusText = I18n.tr("Opening terminal: ") + I18n.tr("Activate") + "...";
|
||||
greeterInstallActionRunning = true;
|
||||
greeterInstallActionProcess.running = true;
|
||||
}
|
||||
|
||||
function promptGreeterActionConfirm() {
|
||||
if (!root.greeterActionAvailable)
|
||||
return;
|
||||
|
||||
greeterActionConfirm.showWithOptions({
|
||||
"title": I18n.tr("Activate Greeter", "greeter action confirmation"),
|
||||
"message": I18n.tr("Activate the DMS greeter? A terminal will open for sudo authentication. Run Sync after activation to apply your settings."),
|
||||
@@ -90,13 +182,20 @@ Item {
|
||||
Qt.callLater(checkGreeterInstallState);
|
||||
}
|
||||
|
||||
Process {
|
||||
id: greetdEnabledCheckProcess
|
||||
command: ["systemctl", "is-enabled", "greetd"]
|
||||
running: false
|
||||
function showWidgetBrowser() {
|
||||
greeterWidgetBrowserLoader.active = true;
|
||||
greeterWidgetBrowserLoader.item?.show();
|
||||
}
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: root.greeterEnabled = text.trim() === "enabled"
|
||||
LazyLoader {
|
||||
id: greeterWidgetBrowserLoader
|
||||
active: false
|
||||
|
||||
DesktopWidgetBrowser {
|
||||
parentModal: root.parentModal
|
||||
listKey: "greeterWidgetInstances"
|
||||
title: I18n.tr("Add widget")
|
||||
onWidgetAdded: ToastService.showInfo(I18n.tr("Widget added"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +254,7 @@ Item {
|
||||
|
||||
Process {
|
||||
id: greeterInstallActionProcess
|
||||
command: root.greeterActionCommand
|
||||
command: ["dms-greeter", "enable", "--terminal"]
|
||||
running: false
|
||||
|
||||
onExited: exitCode => {
|
||||
@@ -177,42 +276,21 @@ Item {
|
||||
iconName: "info"
|
||||
title: I18n.tr("Status")
|
||||
settingKey: "greeterStatus"
|
||||
tags: ["greeter", "login", "sync", "status", "link"]
|
||||
|
||||
SettingsRow {
|
||||
subtitle: I18n.tr("Sync applies your theme and settings to the login screen. Shared users should run dms-greeter sync --profile instead of a primary user sync.")
|
||||
iconName: root.greeterStateIcon
|
||||
iconColor: root.greeterStateWarn ? Theme.warning : Theme.primary
|
||||
title: root.greeterStateTitle
|
||||
subtitle: root.greeterStateSubtitle
|
||||
|
||||
body: Flow {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
layoutDirection: Qt.RightToLeft
|
||||
|
||||
DankButton {
|
||||
text: I18n.tr("Sync", "verb, button that copies settings to the login greeter")
|
||||
iconName: "sync"
|
||||
busy: root.greeterSyncRunning
|
||||
enabled: root.greeterBinaryExists && !root.greeterSyncRunning && !root.greeterInstallActionRunning
|
||||
onClicked: GreeterService.sync()
|
||||
}
|
||||
|
||||
DankButton {
|
||||
text: I18n.tr("Check status", "greeter settings button, runs dms-greeter status")
|
||||
iconName: "fact_check"
|
||||
backgroundColor: Theme.secondaryContainer
|
||||
textColor: Theme.onSecondaryContainer
|
||||
busy: root.greeterStatusRunning
|
||||
enabled: !root.greeterStatusRunning
|
||||
onClicked: root.runGreeterStatus()
|
||||
}
|
||||
|
||||
DankButton {
|
||||
visible: root.greeterActionAvailable
|
||||
text: root.greeterActionLabel
|
||||
iconName: root.greeterActionIcon
|
||||
backgroundColor: Theme.secondaryContainer
|
||||
textColor: Theme.onSecondaryContainer
|
||||
enabled: !root.greeterInstallActionRunning && !root.greeterSyncRunning
|
||||
onClicked: root.promptGreeterActionConfirm()
|
||||
}
|
||||
DankButton {
|
||||
visible: root.greeterStateAction !== ""
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.greeterStateAction
|
||||
busy: root.greeterSyncRunning
|
||||
enabled: !root.busy
|
||||
onClicked: root.runStateAction()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,6 +298,7 @@ Item {
|
||||
visible: root.greeterStatusOutput !== ""
|
||||
noteIconName: ""
|
||||
monospace: true
|
||||
maxHeight: SettingsMetrics.noteMaxHeight
|
||||
text: root.greeterStatusOutput
|
||||
tint: root.greeterStatusRunning ? Theme.surfaceVariantText : Theme.surfaceText
|
||||
tintBackground: SettingsMetrics.controlColor
|
||||
@@ -236,6 +315,57 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "widgets"
|
||||
title: I18n.tr("Widgets")
|
||||
settingKey: "greeterWidgets"
|
||||
tags: ["greeter", "login", "widgets", "clock", "session", "layout"]
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "greeterFollowLockScreen"
|
||||
tags: ["greeter", "login", "lock", "widgets", "layout", "follow"]
|
||||
text: I18n.tr("Follow lock screen", "greeter widgets toggle")
|
||||
description: I18n.tr("The login screen shows the lock screen widgets it supports and its own session picker. Turn off to arrange it separately.", "greeter widgets toggle")
|
||||
checked: SettingsData.greeterFollowLockScreen
|
||||
onToggled: checked => SettingsData.setGreeterFollowLockScreen(checked)
|
||||
}
|
||||
|
||||
SettingsReorderList {
|
||||
id: greeterWidgetList
|
||||
|
||||
model: SettingsData.greeterWidgetInstances || []
|
||||
|
||||
delegate: DesktopWidgetInstanceCard {
|
||||
required property var modelData
|
||||
|
||||
reorderList: greeterWidgetList
|
||||
reorderEnabled: false
|
||||
instanceData: modelData
|
||||
fixed: SettingsData.greeterFollowLockScreen || modelData.widgetType === "lockAuth" || modelData.widgetType === "greeterSession"
|
||||
|
||||
onConfigureRequested: {
|
||||
SettingsUiState.selectedDesktopWidgetId = instanceId;
|
||||
SettingsUiState.selectedWidgetTitle = widgetName;
|
||||
root.parentModal?.navigateTo("desktop_widget");
|
||||
}
|
||||
onDuplicateRequested: SettingsData.duplicateDesktopWidgetInstance(instanceId)
|
||||
onDeleteRequested: {
|
||||
SettingsData.removeDesktopWidgetInstance(instanceId);
|
||||
ToastService.showInfo(I18n.tr("Widget removed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsRow {
|
||||
iconName: "restart_alt"
|
||||
title: I18n.tr("Reset to default")
|
||||
visible: !SettingsData.greeterFollowLockScreen
|
||||
clickable: true
|
||||
onClicked: SettingsData.resetGreeterWidgets()
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
title: I18n.tr("Appearance")
|
||||
settingKey: "greeterAppearance"
|
||||
@@ -297,6 +427,44 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "tune"
|
||||
title: I18n.tr("Advanced")
|
||||
settingKey: "greeterAdvanced"
|
||||
tags: ["greeter", "login", "sync", "status", "repair"]
|
||||
collapsible: true
|
||||
expanded: false
|
||||
|
||||
SettingsRow {
|
||||
iconName: "sync"
|
||||
title: I18n.tr("Run full sync", "greeter advanced action")
|
||||
subtitle: I18n.tr("Re-applies the greetd command, permissions and PAM authentication. Needs administrator access.", "greeter advanced action")
|
||||
clickable: true
|
||||
enabled: root.greeterBinaryExists && !root.busy
|
||||
onClicked: GreeterService.sync()
|
||||
}
|
||||
|
||||
SettingsRow {
|
||||
iconName: "fact_check"
|
||||
title: I18n.tr("Check status", "greeter settings button, runs dms-greeter status")
|
||||
clickable: true
|
||||
enabled: root.greeterBinaryExists && !root.greeterStatusRunning
|
||||
onClicked: root.runGreeterStatus()
|
||||
}
|
||||
|
||||
SettingsRow {
|
||||
iconName: "link_off"
|
||||
iconColor: Theme.error
|
||||
title: I18n.tr("Unlink login screen", "greeter advanced action")
|
||||
subtitle: I18n.tr("Stops syncing your DMS settings to the greeter. Set up links them again.", "greeter advanced action")
|
||||
visible: GreeterService.slotState !== ""
|
||||
clickable: true
|
||||
enabled: root.greeterBinaryExists && !root.busy
|
||||
onClicked: root.promptUnlink()
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "extension"
|
||||
@@ -336,8 +504,44 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
GreeterSyncFabBar {
|
||||
blocked: root.greeterInstallActionRunning
|
||||
SettingsFabBar {
|
||||
id: greeterFabs
|
||||
|
||||
readonly property bool syncPending: SessionData.greeterSyncPending && GreeterService.binaryExists
|
||||
|
||||
DankFab {
|
||||
visible: greeterFabs.syncPending
|
||||
text: I18n.tr("Revert")
|
||||
iconName: "undo"
|
||||
colorRole: "secondaryContainer"
|
||||
enabled: !GreeterService.syncing
|
||||
onClicked: SettingsData.revertGreeterSyncPending()
|
||||
}
|
||||
|
||||
DankFab {
|
||||
visible: greeterFabs.syncPending
|
||||
text: GreeterService.syncing ? I18n.tr("Syncing...", "greeter settings status while sync is running") : I18n.tr("Apply changes")
|
||||
iconName: "check"
|
||||
colorRole: "primary"
|
||||
busy: GreeterService.syncing
|
||||
enabled: !root.busy
|
||||
onClicked: GreeterService.sync()
|
||||
}
|
||||
|
||||
DankFab {
|
||||
visible: !greeterFabs.syncPending
|
||||
text: I18n.tr("Edit widgets")
|
||||
iconName: "edit"
|
||||
colorRole: SettingsData.greeterFollowLockScreen ? "primary" : "secondaryContainer"
|
||||
onClicked: SessionService.greeterEditorRequested()
|
||||
}
|
||||
|
||||
DankFab {
|
||||
visible: !greeterFabs.syncPending && !SettingsData.greeterFollowLockScreen
|
||||
text: I18n.tr("Add widget")
|
||||
iconName: "add"
|
||||
onClicked: root.showWidgetBrowser()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ DesktopWidgetInstanceSettings {
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
visible: !root.greeterInstance
|
||||
text: I18n.tr("Show media player")
|
||||
checked: root.cfg.showMediaPlayer ?? true
|
||||
onToggled: checked => root.updateConfig("showMediaPlayer", checked)
|
||||
|
||||
@@ -44,6 +44,7 @@ Singleton {
|
||||
type: "builtin",
|
||||
component: "qs.Modules.BuiltinDesktopPlugins.DesktopClockWidget",
|
||||
settingsComponent: "qs.Modules.Settings.DesktopWidgetSettings.ClockSettings",
|
||||
greeter: true,
|
||||
defaultConfig: getDefaultClockConfig(),
|
||||
defaultSize: {
|
||||
width: 280,
|
||||
@@ -68,24 +69,40 @@ Singleton {
|
||||
}
|
||||
|
||||
function registerLockBuiltins() {
|
||||
const lockWidget = (id, name, icon) => registerWidget({
|
||||
const lockWidget = (id, name, icon, greeter) => registerWidget({
|
||||
id: id,
|
||||
name: name,
|
||||
icon: icon,
|
||||
description: "",
|
||||
type: "builtin",
|
||||
lockOnly: true,
|
||||
greeter: greeter,
|
||||
defaultConfig: Spec.SPEC.lockScreenWidgetInstances.def.find(inst => inst.widgetType === id)?.config ?? {},
|
||||
defaultSize: {
|
||||
width: 200,
|
||||
height: 200
|
||||
}
|
||||
});
|
||||
lockWidget("lockDate", I18n.tr("Date"), "event");
|
||||
lockWidget("lockAuth", I18n.tr("Password"), "lock");
|
||||
lockWidget("lockNotifications", I18n.tr("Notifications"), "notifications");
|
||||
lockWidget("lockStatus", I18n.tr("Status"), "wifi");
|
||||
lockWidget("lockPower", I18n.tr("Power"), "power_settings_new");
|
||||
lockWidget("lockDate", I18n.tr("Date"), "event", true);
|
||||
lockWidget("lockAuth", I18n.tr("Password"), "lock", true);
|
||||
lockWidget("lockNotifications", I18n.tr("Notifications"), "notifications", false);
|
||||
lockWidget("lockStatus", I18n.tr("Status"), "wifi", true);
|
||||
lockWidget("lockPower", I18n.tr("Power"), "power_settings_new", true);
|
||||
registerWidget({
|
||||
id: "greeterSession",
|
||||
name: I18n.tr("Session", "greeter session picker widget"),
|
||||
icon: "login",
|
||||
description: "",
|
||||
type: "builtin",
|
||||
lockOnly: true,
|
||||
greeterOnly: true,
|
||||
greeter: true,
|
||||
defaultConfig: Spec.greeterSessionDefault().config,
|
||||
defaultSize: {
|
||||
width: 200,
|
||||
height: 48
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getDefaultClockConfig() {
|
||||
@@ -255,6 +272,24 @@ Singleton {
|
||||
return registeredWidgetsList.filter(w => !w.lockOnly);
|
||||
}
|
||||
|
||||
function getLockWidgets() {
|
||||
return registeredWidgetsList.filter(w => !w.greeterOnly);
|
||||
}
|
||||
|
||||
function getGreeterWidgets() {
|
||||
return registeredWidgetsList.filter(w => w.greeter);
|
||||
}
|
||||
|
||||
function getListWidgets(listKey) {
|
||||
switch (listKey) {
|
||||
case "lockScreenWidgetInstances":
|
||||
return getLockWidgets();
|
||||
case "greeterWidgetInstances":
|
||||
return getGreeterWidgets();
|
||||
}
|
||||
return getDesktopWidgets();
|
||||
}
|
||||
|
||||
function getPluginWidgets() {
|
||||
return registeredWidgetsList.filter(w => w.type === "plugin");
|
||||
}
|
||||
|
||||
@@ -12,10 +12,18 @@ Singleton {
|
||||
|
||||
property bool available: false
|
||||
property bool binaryExists: false
|
||||
property bool configured: false
|
||||
property bool enabled: false
|
||||
property bool inGroup: false
|
||||
// "live" when the greeter slot symlinks this user's DMS files, "snapshot" for a copied slot, "" when absent
|
||||
property string slotState: ""
|
||||
property bool syncing: false
|
||||
property string syncStatus: ""
|
||||
property bool fallbackFromPrecheck: false
|
||||
|
||||
readonly property bool linked: slotState === "live"
|
||||
readonly property bool profileSyncSufficient: configured && inGroup
|
||||
|
||||
signal syncFinished
|
||||
|
||||
function refresh() {
|
||||
@@ -31,6 +39,19 @@ Singleton {
|
||||
sudoProbeProcess.running = true;
|
||||
}
|
||||
|
||||
// Links this user's slot without touching system config when the greeter is already installed.
|
||||
function link() {
|
||||
if (!profileSyncSufficient) {
|
||||
sync();
|
||||
return;
|
||||
}
|
||||
if (!binaryExists || syncing)
|
||||
return;
|
||||
syncStatus = "";
|
||||
syncing = true;
|
||||
profileSyncProcess.running = true;
|
||||
}
|
||||
|
||||
function launchTerminalFallback(fromPrecheck, statusText) {
|
||||
fallbackFromPrecheck = fromPrecheck;
|
||||
if (statusText)
|
||||
@@ -38,6 +59,14 @@ Singleton {
|
||||
terminalFallbackProcess.running = true;
|
||||
}
|
||||
|
||||
function unlink() {
|
||||
if (!binaryExists || syncing)
|
||||
return;
|
||||
syncStatus = "";
|
||||
syncing = true;
|
||||
unlinkProcess.running = true;
|
||||
}
|
||||
|
||||
function withOutput(message, out, err) {
|
||||
let text = message;
|
||||
if (out !== "")
|
||||
@@ -49,24 +78,65 @@ Singleton {
|
||||
|
||||
function finishSync() {
|
||||
syncing = false;
|
||||
refresh();
|
||||
syncFinished();
|
||||
}
|
||||
|
||||
Process {
|
||||
id: detectProcess
|
||||
|
||||
command: ["sh", "-c", "command -v dms-greeter >/dev/null 2>&1 && echo binary; grep -qs dms-greeter /etc/greetd/config.toml && echo config"]
|
||||
command: ["sh", "-c", `command -v dms-greeter >/dev/null 2>&1 && echo binary
|
||||
grep -qs dms-greeter /etc/greetd/config.toml && echo config
|
||||
[ "$(systemctl is-enabled greetd 2>/dev/null)" = enabled ] && echo enabled
|
||||
id -nG | tr ' ' '\n' | grep -qxE 'greeter|greetd|_greeter' && echo group
|
||||
own=$(readlink -f "$HOME/.config/DankMaterialShell/settings.json")
|
||||
for f in "/var/cache/dms-greeter/users/$USER/settings.json" /var/cache/dms-greeter/settings.json; do
|
||||
[ -L "$f" ] && [ "$(readlink -f "$f")" = "$own" ] && echo live && break
|
||||
done
|
||||
[ -f "/var/cache/dms-greeter/users/$USER/settings.json" ] && echo slot
|
||||
exit 0`]
|
||||
running: true
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const found = text.split("\n");
|
||||
root.binaryExists = found.includes("binary");
|
||||
root.available = root.binaryExists || found.includes("config");
|
||||
root.configured = found.includes("config");
|
||||
root.enabled = found.includes("enabled");
|
||||
root.inGroup = found.includes("group");
|
||||
root.slotState = found.includes("live") ? "live" : (found.includes("slot") ? "snapshot" : "");
|
||||
root.available = root.binaryExists || root.configured;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: profileSyncProcess
|
||||
|
||||
command: ["dms-greeter", "sync", "--profile", "--yes"]
|
||||
|
||||
stdout: StdioCollector {
|
||||
id: profileOut
|
||||
}
|
||||
|
||||
stderr: StdioCollector {
|
||||
id: profileErr
|
||||
}
|
||||
|
||||
onExited: exitCode => {
|
||||
const out = profileOut.text.trim();
|
||||
const err = profileErr.text.trim();
|
||||
if (exitCode !== 0) {
|
||||
root.syncStatus = root.withOutput(I18n.tr("Profile sync failed.") + " (exit " + exitCode + ")", out, err);
|
||||
root.finishSync();
|
||||
return;
|
||||
}
|
||||
root.syncStatus = root.withOutput(I18n.tr("Sync completed successfully."), out, err);
|
||||
ToastService.showInfo(I18n.tr("Greeter sync complete"));
|
||||
root.finishSync();
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: sudoProbeProcess
|
||||
|
||||
@@ -118,6 +188,44 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: unlinkProcess
|
||||
|
||||
command: ["dms-greeter", "unlink", "--yes"]
|
||||
|
||||
stdout: StdioCollector {
|
||||
id: unlinkOut
|
||||
}
|
||||
|
||||
stderr: StdioCollector {
|
||||
id: unlinkErr
|
||||
}
|
||||
|
||||
onExited: exitCode => {
|
||||
const out = unlinkOut.text.trim();
|
||||
const err = unlinkErr.text.trim();
|
||||
if (exitCode === 0) {
|
||||
root.syncStatus = root.withOutput(I18n.tr("Login screen unlinked.", "greeter unlink status"), out, err);
|
||||
root.finishSync();
|
||||
return;
|
||||
}
|
||||
root.syncStatus = root.withOutput(I18n.tr("Unlink needs sudo authentication. Opening terminal so you can use password or fingerprint.", "greeter unlink status"), out, err);
|
||||
unlinkTerminalProcess.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: unlinkTerminalProcess
|
||||
|
||||
command: ["dms-greeter", "unlink", "--terminal", "--yes"]
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode !== 0)
|
||||
root.syncStatus += "\n\n" + I18n.tr("Terminal fallback failed. Install one of the supported terminal emulators or run 'dms-greeter unlink' manually.", "greeter unlink status") + " (exit " + exitCode + ")";
|
||||
root.finishSync();
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: terminalFallbackProcess
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ Singleton {
|
||||
signal sessionUnlocked
|
||||
signal sessionResumed
|
||||
signal lockEditorRequested
|
||||
signal greeterEditorRequested
|
||||
signal lidOpened
|
||||
signal loginctlStateChanged
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import qs.DankCommon.Widgets as DankCommon
|
||||
|
||||
DankCommon.DankClockWidget {}
|
||||
@@ -11502,6 +11502,26 @@
|
||||
],
|
||||
"parentLabel": "Date, time & region"
|
||||
},
|
||||
{
|
||||
"section": "greeterAdvanced",
|
||||
"label": "Advanced",
|
||||
"tabIndex": 31,
|
||||
"category": "Greeter",
|
||||
"keywords": [
|
||||
"advanced",
|
||||
"display manager",
|
||||
"greetd",
|
||||
"greeter",
|
||||
"login",
|
||||
"repair",
|
||||
"security",
|
||||
"status",
|
||||
"sync"
|
||||
],
|
||||
"parentLabel": "Security",
|
||||
"icon": "tune",
|
||||
"conditionKey": "greeterAvailable"
|
||||
},
|
||||
{
|
||||
"section": "greeterAppearance",
|
||||
"label": "Appearance",
|
||||
@@ -11611,6 +11631,37 @@
|
||||
"icon": "extension",
|
||||
"conditionKey": "greeterAvailable"
|
||||
},
|
||||
{
|
||||
"section": "greeterFollowLockScreen",
|
||||
"label": "Follow lock screen",
|
||||
"tabIndex": 31,
|
||||
"category": "Greeter",
|
||||
"keywords": [
|
||||
"arrange",
|
||||
"components",
|
||||
"display manager",
|
||||
"follow",
|
||||
"greetd",
|
||||
"greeter",
|
||||
"layout",
|
||||
"lock",
|
||||
"lockscreen",
|
||||
"login",
|
||||
"modules",
|
||||
"picker",
|
||||
"screen",
|
||||
"security",
|
||||
"separately",
|
||||
"session",
|
||||
"shows",
|
||||
"supports",
|
||||
"turn",
|
||||
"widgets"
|
||||
],
|
||||
"parentLabel": "Security",
|
||||
"description": "The login screen shows the lock screen widgets it supports and its own session picker. Turn off to arrange it separately.",
|
||||
"conditionKey": "greeterAvailable"
|
||||
},
|
||||
{
|
||||
"section": "_tab_31",
|
||||
"label": "Greeter",
|
||||
@@ -11677,14 +11728,48 @@
|
||||
"display manager",
|
||||
"greetd",
|
||||
"greeter",
|
||||
"link",
|
||||
"login",
|
||||
"security",
|
||||
"status"
|
||||
"status",
|
||||
"sync"
|
||||
],
|
||||
"parentLabel": "Security",
|
||||
"icon": "info",
|
||||
"conditionKey": "greeterAvailable"
|
||||
},
|
||||
{
|
||||
"section": "greeterWidgets",
|
||||
"label": "Widgets",
|
||||
"tabIndex": 31,
|
||||
"category": "Greeter",
|
||||
"keywords": [
|
||||
"arrange",
|
||||
"clock",
|
||||
"components",
|
||||
"display manager",
|
||||
"greetd",
|
||||
"greeter",
|
||||
"layout",
|
||||
"lock",
|
||||
"lockscreen",
|
||||
"login",
|
||||
"modules",
|
||||
"picker",
|
||||
"screen",
|
||||
"security",
|
||||
"separately",
|
||||
"session",
|
||||
"shows",
|
||||
"supports",
|
||||
"turn",
|
||||
"widgets"
|
||||
],
|
||||
"parentLabel": "Security",
|
||||
"icon": "widgets",
|
||||
"description": "The login screen shows the lock screen widgets it supports and its own session picker. Turn off to arrange it separately.",
|
||||
"conditionKey": "greeterAvailable"
|
||||
},
|
||||
{
|
||||
"section": "muxType",
|
||||
"label": "General",
|
||||
|
||||
Reference in New Issue
Block a user