Implement custom layout persistence

Features:
- Custom layouts now saved to ~/.local/share/gridsnap/layouts.json
- Layouts automatically loaded on extension initialization
- Layouts persist across Cinnamon restarts and extension reloads
- Only custom layouts saved to file (default layouts remain in code)
- Added GLib and Gio imports for file operations
- Error handling for file read/write operations

Technical implementation:
- _loadLayouts(): Merges default layouts with saved custom layouts on startup
- _saveLayouts(): Extracts and saves only custom-* layouts to JSON file
- Automatically creates storage directory if it doesn't exist
- Called when user saves a layout in the graphical editor

Fixes TODO item #1 - Custom Layout Persistence

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-15 21:02:58 -07:00
parent 30db2e3ed9
commit 19f7c7faec
2 changed files with 73 additions and 12 deletions

View File

@@ -5,9 +5,15 @@ const Clutter = imports.gi.Clutter;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Settings = imports.ui.settings;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
let zoneManager;
// Storage path for custom layouts
const STORAGE_DIR = GLib.get_user_data_dir() + '/gridsnap';
const LAYOUTS_FILE = STORAGE_DIR + '/layouts.json';
// Default zone layouts
const DEFAULT_LAYOUTS = {
'grid-2x2': {
@@ -291,11 +297,11 @@ ZoneEditor.prototype = {
zones: this.zones
};
zoneManager.currentLayout = layoutId;
// Persist to file for permanent storage
zoneManager._saveLayouts();
Main.notify('GridSnap', 'Layout saved: ' + layoutName);
// TODO: Persist to file for permanent storage
global.log('GridSnap: Layout saved - ' + JSON.stringify(zoneManager.layouts[layoutId]));
}
this._cancelEditor();
@@ -330,12 +336,15 @@ ZoneManager.prototype = {
_init: function() {
this.overlay = null;
this.currentLayout = 'grid-2x2';
this.layouts = DEFAULT_LAYOUTS;
this.layouts = {};
this.isShowing = false;
this.dragInProgress = false;
this.draggedWindow = null;
this.editor = new ZoneEditor();
// Load layouts (default + saved custom layouts)
this._loadLayouts();
// Connect to window grab events
this._connectSignals();
this._setupKeybindings();
@@ -379,6 +388,58 @@ ZoneManager.prototype = {
);
}
},
_loadLayouts: function() {
// Start with default layouts
for (let layoutId in DEFAULT_LAYOUTS) {
this.layouts[layoutId] = DEFAULT_LAYOUTS[layoutId];
}
// Try to load custom layouts from file
try {
let file = Gio.File.new_for_path(LAYOUTS_FILE);
if (file.query_exists(null)) {
let [success, contents] = file.load_contents(null);
if (success) {
let customLayouts = JSON.parse(contents.toString());
// Merge custom layouts with defaults
for (let layoutId in customLayouts) {
this.layouts[layoutId] = customLayouts[layoutId];
}
global.log('GridSnap: Loaded ' + Object.keys(customLayouts).length + ' custom layout(s)');
}
}
} catch(e) {
global.logError('GridSnap: Error loading custom layouts - ' + e);
}
},
_saveLayouts: function() {
try {
// Create directory if it doesn't exist
let dir = Gio.File.new_for_path(STORAGE_DIR);
if (!dir.query_exists(null)) {
dir.make_directory_with_parents(null);
}
// Extract only custom layouts (those starting with 'custom-')
let customLayouts = {};
for (let layoutId in this.layouts) {
if (layoutId.startsWith('custom-')) {
customLayouts[layoutId] = this.layouts[layoutId];
}
}
// Save to file
let file = Gio.File.new_for_path(LAYOUTS_FILE);
let contents = JSON.stringify(customLayouts, null, 2);
file.replace_contents(contents, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null);
global.log('GridSnap: Saved ' + Object.keys(customLayouts).length + ' custom layout(s) to ' + LAYOUTS_FILE);
} catch(e) {
global.logError('GridSnap: Error saving custom layouts - ' + e);
}
},
_checkModifiersDuringDrag: function() {
if (!this.dragInProgress) {