Skip to content

Commit bd62c13

Browse files
fuddlesworthruvnet
andcommitted
fix: restore system layout when user override is deleted from KCM
Layout now tracks its system origin path (systemSourcePath) so that deleting a user override of a system layout instantly restores the original without a daemon restart or filesystem scanning. - Add m_systemSourcePath to Layout, persisted in user JSON - Capture system path in saveLayout before sourcePath switches to user dir - Capture system path in loadLayoutsFromDirectory during override detection - restoreSystemLayout reloads directly from stored path - Clear m_systemSourcePath in operator= (copies have no system origin) - Fix pre-existing m_previousLayout dangling pointer on layout removal - Make KCM deleteLayout synchronous to avoid race with loadLayouts Co-Authored-By: claude-flow <ruv@ruv.net>
1 parent 9c6c8c7 commit bd62c13

File tree

6 files changed

+104
-33
lines changed

6 files changed

+104
-33
lines changed

kcm/kcm_plasmazones.cpp

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1780,12 +1780,8 @@ void KCMPlasmaZones::createNewLayout()
17801780

17811781
void KCMPlasmaZones::deleteLayout(const QString& layoutId)
17821782
{
1783-
QDBusMessage msg =
1784-
QDBusMessage::createMethodCall(QString(DBus::ServiceName), QString(DBus::ObjectPath),
1785-
QString(DBus::Interface::LayoutManager), QStringLiteral("deleteLayout"));
1786-
msg << layoutId;
1787-
watchAsyncDbusCall(QDBusConnection::sessionBus().asyncCall(msg), QStringLiteral("deleteLayout"));
1788-
QTimer::singleShot(100, this, &KCMPlasmaZones::loadLayouts);
1783+
callDaemon(QString(DBus::Interface::LayoutManager), QStringLiteral("deleteLayout"), {layoutId});
1784+
loadLayouts();
17891785
}
17901786

17911787
void KCMPlasmaZones::duplicateLayout(const QString& layoutId)

src/core/constants.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ inline constexpr QLatin1String OuterGap{"outerGap"};
132132
inline constexpr QLatin1String ShowZoneNumbers{"showZoneNumbers"};
133133
inline constexpr QLatin1String IsBuiltIn{"isBuiltIn"}; // Legacy, for backward compat when loading
134134
inline constexpr QLatin1String IsSystem{"isSystem"}; // New: determined by source path
135+
inline constexpr QLatin1String SystemSourcePath{"systemSourcePath"}; // Original system layout path (for user overrides)
135136
inline constexpr QLatin1String ZoneCount{"zoneCount"};
136137
inline constexpr QLatin1String Category{"category"}; // LayoutCategory: 0=Manual
137138

src/core/layout.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ Layout& Layout::operator=(const Layout& other)
129129
m_showZoneNumbers = other.m_showZoneNumbers;
130130
m_defaultOrder = other.m_defaultOrder;
131131
m_sourcePath.clear(); // Assignment creates a user copy (will be saved to user directory)
132+
m_systemSourcePath.clear(); // New copy has no system origin
132133
m_shaderId = other.m_shaderId;
133134
m_shaderParams = other.m_shaderParams;
134135
bool rulesChanged = m_appRules != other.m_appRules;
@@ -559,6 +560,11 @@ QJsonObject Layout::toJson() const
559560
}
560561
// Note: isBuiltIn is no longer serialized - it's determined by source path at load time
561562

563+
// Persist system origin path so user overrides can be restored on deletion
564+
if (!m_systemSourcePath.isEmpty()) {
565+
json[JsonKeys::SystemSourcePath] = m_systemSourcePath;
566+
}
567+
562568
// Shader support - only persist params belonging to the active shader
563569
if (!ShaderRegistry::isNoneShader(m_shaderId)) {
564570
json[JsonKeys::ShaderId] = m_shaderId;
@@ -635,6 +641,8 @@ Layout* Layout::fromJson(const QJsonObject& json, QObject* parent)
635641
layout->m_showZoneNumbers = json[JsonKeys::ShowZoneNumbers].toBool(true);
636642
layout->m_defaultOrder = json[JsonKeys::DefaultOrder].toInt(999);
637643
// Note: sourcePath is set by LayoutManager after loading, not from JSON
644+
// But systemSourcePath IS persisted in user JSON for system override restoration
645+
layout->m_systemSourcePath = json[JsonKeys::SystemSourcePath].toString();
638646

639647
// Shader support
640648
layout->m_shaderId = json[JsonKeys::ShaderId].toString();

src/core/layout.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,12 @@ class PLASMAZONES_EXPORT Layout : public QObject
228228
// This determines whether the layout can be edited/deleted in place
229229
bool isSystemLayout() const;
230230

231+
// Original system layout path — set when a user override replaces a system layout.
232+
// Persisted in user JSON so deletion can restore the system original without scanning.
233+
QString systemSourcePath() const { return m_systemSourcePath; }
234+
void setSystemSourcePath(const QString& path) { m_systemSourcePath = path; }
235+
bool hasSystemOrigin() const { return !m_systemSourcePath.isEmpty(); }
236+
231237
// Shader support
232238
QString shaderId() const
233239
{
@@ -369,6 +375,7 @@ class PLASMAZONES_EXPORT Layout : public QObject
369375
int m_outerGapRight = -1;
370376
bool m_showZoneNumbers = true;
371377
QString m_sourcePath; // Path where layout was loaded from (empty for new layouts)
378+
QString m_systemSourcePath; // Original system path if this is a user override of a system layout
372379
int m_defaultOrder = 999; // Optional: lower values appear first when choosing default (999 = not set)
373380
QVector<Zone*> m_zones;
374381

src/core/layoutmanager.cpp

Lines changed: 85 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -201,47 +201,60 @@ void LayoutManager::removeLayout(Layout* layout)
201201
return;
202202
}
203203

204-
// Store ID and state BEFORE any operations that might invalidate the pointer
204+
// Store state BEFORE any operations that might invalidate the pointer
205205
const QUuid layoutId = layout->id();
206206
const bool wasActive = (m_activeLayout == layout);
207207
const QString filePath = layoutFilePath(layoutId);
208+
const QString systemPath = layout->systemSourcePath();
208209

209210
// Remove from layouts list
210211
m_layouts.removeOne(layout);
211212

212-
// Remove any assignments using stored ID (safe - ID is copied)
213-
for (auto it = m_assignments.begin(); it != m_assignments.end();) {
214-
if (it.value() == layoutId) {
215-
it = m_assignments.erase(it);
216-
} else {
217-
++it;
218-
}
219-
}
213+
// Delete user layout file
214+
QFile::remove(filePath);
220215

221-
// Remove from quick shortcuts using stored ID
222-
for (auto it = m_quickLayoutShortcuts.begin(); it != m_quickLayoutShortcuts.end();) {
223-
if (it.value() == layoutId) {
224-
it = m_quickLayoutShortcuts.erase(it);
225-
} else {
226-
++it;
227-
}
216+
// Clear stale pointer before deletion
217+
if (m_previousLayout == layout) {
218+
m_previousLayout = nullptr;
228219
}
229220

230-
// Update active layout if needed
231-
if (wasActive) {
232-
setActiveLayout(defaultLayout());
233-
}
221+
Q_EMIT layoutRemoved(layout);
222+
layout->deleteLater();
234223

235-
// Delete layout file (using stored path)
236-
QFile::remove(filePath);
224+
// If this was a user override of a system layout, restore the system original.
225+
// Uses the stored system path — no filesystem scanning needed.
226+
Layout* restored = restoreSystemLayout(layoutId, systemPath);
237227

238-
// Emit signals before deleting
239-
Q_EMIT layoutRemoved(layout);
240-
Q_EMIT layoutsChanged();
228+
if (restored) {
229+
// System layout restored — assignments and shortcuts stay valid (same UUID).
230+
// If deleted layout was active, activate the restored system layout.
231+
if (wasActive) {
232+
setActiveLayout(restored);
233+
}
234+
} else {
235+
// Truly deleted — clean up assignments and shortcuts referencing this layout
236+
for (auto it = m_assignments.begin(); it != m_assignments.end();) {
237+
if (it.value() == layoutId) {
238+
it = m_assignments.erase(it);
239+
} else {
240+
++it;
241+
}
242+
}
241243

242-
// Schedule deletion (safe - we've already removed from all containers)
243-
layout->deleteLater();
244+
for (auto it = m_quickLayoutShortcuts.begin(); it != m_quickLayoutShortcuts.end();) {
245+
if (it.value() == layoutId) {
246+
it = m_quickLayoutShortcuts.erase(it);
247+
} else {
248+
++it;
249+
}
250+
}
251+
252+
if (wasActive) {
253+
setActiveLayout(defaultLayout());
254+
}
255+
}
244256

257+
Q_EMIT layoutsChanged();
245258
saveAssignments();
246259
}
247260

@@ -745,6 +758,10 @@ void LayoutManager::loadLayoutsFromDirectory(const QString& directory)
745758
// if we find a duplicate, the new one is from user directory and should replace
746759
if (!layout->isSystemLayout() && existing->isSystemLayout()) {
747760
// User layout overrides system layout - replace the existing one
761+
// Preserve the system origin path so we can restore on deletion
762+
if (layout->systemSourcePath().isEmpty()) {
763+
layout->setSystemSourcePath(existing->sourcePath());
764+
}
748765
int index = m_layouts.indexOf(existing);
749766
disconnect(existing, &Layout::layoutModified, this, nullptr);
750767
m_layouts.replace(index, layout);
@@ -772,6 +789,13 @@ void LayoutManager::saveLayout(Layout* layout)
772789

773790
ensureLayoutDirectory();
774791

792+
// If this is a system layout being saved to user dir for the first time,
793+
// capture the system origin path before toJson/sourcePath changes
794+
if (layout->isSystemLayout() && !layout->hasSystemOrigin()) {
795+
layout->setSystemSourcePath(layout->sourcePath());
796+
qCInfo(lcLayout) << "Captured system origin for" << layout->name() << "from=" << layout->sourcePath();
797+
}
798+
775799
const QString filePath = layoutFilePath(layout->id());
776800
QFile file(filePath);
777801

@@ -781,6 +805,7 @@ void LayoutManager::saveLayout(Layout* layout)
781805
return;
782806
}
783807

808+
// toJson() includes systemSourcePath so it persists across daemon restarts
784809
QJsonDocument doc(layout->toJson());
785810
const QByteArray data = doc.toJson(QJsonDocument::Indented);
786811

@@ -1037,6 +1062,39 @@ void LayoutManager::exportLayout(Layout* layout, const QString& filePath)
10371062
qCInfo(lcLayout) << "Successfully exported layout:" << layout->name() << "to" << filePath;
10381063
}
10391064

1065+
Layout* LayoutManager::restoreSystemLayout(const QUuid& id, const QString& systemPath)
1066+
{
1067+
if (systemPath.isEmpty() || layoutById(id)) {
1068+
return nullptr;
1069+
}
1070+
1071+
QFile file(systemPath);
1072+
if (!file.exists() || !file.open(QIODevice::ReadOnly)) {
1073+
qCWarning(lcLayout) << "System layout file missing:" << systemPath;
1074+
return nullptr;
1075+
}
1076+
1077+
QJsonParseError parseError;
1078+
const auto doc = QJsonDocument::fromJson(file.readAll(), &parseError);
1079+
if (parseError.error != QJsonParseError::NoError) {
1080+
qCWarning(lcLayout) << "System layout parse error:" << systemPath << parseError.errorString();
1081+
return nullptr;
1082+
}
1083+
1084+
auto* layout = Layout::fromJson(doc.object(), this);
1085+
if (!layout || layout->id() != id) {
1086+
delete layout;
1087+
return nullptr;
1088+
}
1089+
1090+
layout->setSourcePath(systemPath);
1091+
m_layouts.append(layout);
1092+
connect(layout, &Layout::layoutModified, this, [this, layout]() { saveLayout(layout); });
1093+
Q_EMIT layoutAdded(layout);
1094+
qCInfo(lcLayout) << "Restored system layout name=" << layout->name() << "from=" << systemPath;
1095+
return layout;
1096+
}
1097+
10401098
void LayoutManager::ensureLayoutDirectory()
10411099
{
10421100
QDir dir(m_layoutDirectory);

src/core/layoutmanager.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ class PLASMAZONES_EXPORT LayoutManager : public QObject, public ILayoutManager
195195
private:
196196
void ensureLayoutDirectory();
197197
void loadLayoutsFromDirectory(const QString& directory);
198+
Layout* restoreSystemLayout(const QUuid& id, const QString& systemPath);
198199
QString layoutFilePath(const QUuid& id) const;
199200
Layout* cycleLayoutImpl(const QString& screenId, int direction);
200201
bool shouldSkipLayoutAssignment(const QUuid& layoutId, const QString& context) const;

0 commit comments

Comments
 (0)