mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-13 09:12:08 -04:00
plugins: add plugin state helpers
This commit is contained in:
@@ -147,6 +147,24 @@ Item {
|
|||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function saveState(key, value) {
|
||||||
|
if (!pluginService)
|
||||||
|
return;
|
||||||
|
if (pluginService.savePluginState)
|
||||||
|
pluginService.savePluginState(pluginId, key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadState(key, defaultValue) {
|
||||||
|
if (pluginService && pluginService.loadPluginState)
|
||||||
|
return pluginService.loadPluginState(pluginId, key, defaultValue);
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearState() {
|
||||||
|
if (pluginService && pluginService.clearPluginState)
|
||||||
|
pluginService.clearPluginState(pluginId);
|
||||||
|
}
|
||||||
|
|
||||||
function findFlickable(item) {
|
function findFlickable(item) {
|
||||||
var current = item?.parent;
|
var current = item?.parent;
|
||||||
while (current) {
|
while (current) {
|
||||||
|
|||||||
174
quickshell/PLUGINS/QuickNotesExample/QuickNotesLauncher.qml
Normal file
174
quickshell/PLUGINS/QuickNotesExample/QuickNotesLauncher.qml
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var pluginService: null
|
||||||
|
property string trigger: "n"
|
||||||
|
|
||||||
|
signal itemsChanged
|
||||||
|
|
||||||
|
property var notes: []
|
||||||
|
property int maxNotes: 50
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (!pluginService)
|
||||||
|
return;
|
||||||
|
trigger = pluginService.loadPluginData("quickNotesExample", "trigger", "n");
|
||||||
|
maxNotes = pluginService.loadPluginData("quickNotesExample", "maxNotes", 50);
|
||||||
|
|
||||||
|
// Load notes from plugin STATE (persistent across sessions, separate file)
|
||||||
|
notes = pluginService.loadPluginState("quickNotesExample", "notes", []);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getItems(query) {
|
||||||
|
const items = [];
|
||||||
|
|
||||||
|
if (query && query.trim().length > 0) {
|
||||||
|
const text = query.trim();
|
||||||
|
items.push({
|
||||||
|
name: "Save note: " + text,
|
||||||
|
icon: "material:note_add",
|
||||||
|
comment: "Save as a new note",
|
||||||
|
action: "add:" + text,
|
||||||
|
categories: ["Quick Notes"]
|
||||||
|
});
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
name: "Copy: " + text,
|
||||||
|
icon: "material:content_copy",
|
||||||
|
comment: "Copy text to clipboard",
|
||||||
|
action: "copy:" + text,
|
||||||
|
categories: ["Quick Notes"]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredNotes = query ? notes.filter(n => n.text.toLowerCase().includes(query.toLowerCase())) : notes;
|
||||||
|
|
||||||
|
for (let i = 0; i < Math.min(20, filteredNotes.length); i++) {
|
||||||
|
const note = filteredNotes[i];
|
||||||
|
const age = _formatAge(note.timestamp);
|
||||||
|
items.push({
|
||||||
|
name: note.text,
|
||||||
|
icon: "material:sticky_note_2",
|
||||||
|
comment: age + " — select to copy, hold for options",
|
||||||
|
action: "copy:" + note.text,
|
||||||
|
categories: ["Quick Notes"]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notes.length > 0 && !query) {
|
||||||
|
items.push({
|
||||||
|
name: "Clear all notes (" + notes.length + ")",
|
||||||
|
icon: "material:delete_sweep",
|
||||||
|
comment: "Remove all saved notes",
|
||||||
|
action: "clear:",
|
||||||
|
categories: ["Quick Notes"]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeItem(item) {
|
||||||
|
if (!item?.action)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const colonIdx = item.action.indexOf(":");
|
||||||
|
const actionType = item.action.substring(0, colonIdx);
|
||||||
|
const actionData = item.action.substring(colonIdx + 1);
|
||||||
|
|
||||||
|
switch (actionType) {
|
||||||
|
case "add":
|
||||||
|
addNote(actionData);
|
||||||
|
break;
|
||||||
|
case "copy":
|
||||||
|
copyToClipboard(actionData);
|
||||||
|
break;
|
||||||
|
case "remove":
|
||||||
|
removeNote(actionData);
|
||||||
|
break;
|
||||||
|
case "clear":
|
||||||
|
clearAllNotes();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
showToast("Unknown action: " + actionType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addNote(text) {
|
||||||
|
if (!text)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const existing = notes.findIndex(n => n.text === text);
|
||||||
|
if (existing !== -1)
|
||||||
|
notes.splice(existing, 1);
|
||||||
|
|
||||||
|
notes.unshift({
|
||||||
|
text: text,
|
||||||
|
timestamp: Date.now()
|
||||||
|
});
|
||||||
|
|
||||||
|
if (notes.length > maxNotes)
|
||||||
|
notes = notes.slice(0, maxNotes);
|
||||||
|
|
||||||
|
_saveNotes();
|
||||||
|
showToast("Note saved");
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeNote(text) {
|
||||||
|
notes = notes.filter(n => n.text !== text);
|
||||||
|
_saveNotes();
|
||||||
|
showToast("Note removed");
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAllNotes() {
|
||||||
|
notes = [];
|
||||||
|
pluginService.clearPluginState("quickNotesExample");
|
||||||
|
showToast("All notes cleared");
|
||||||
|
itemsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _saveNotes() {
|
||||||
|
if (!pluginService)
|
||||||
|
return;
|
||||||
|
// Save to plugin STATE — writes to quickNotesExample_state.json
|
||||||
|
// This is separate from plugin SETTINGS (plugin_settings.json)
|
||||||
|
pluginService.savePluginState("quickNotesExample", "notes", notes);
|
||||||
|
itemsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyToClipboard(text) {
|
||||||
|
Quickshell.execDetached(["dms", "cl", "copy", text]);
|
||||||
|
showToast("Copied to clipboard");
|
||||||
|
}
|
||||||
|
|
||||||
|
function showToast(message) {
|
||||||
|
if (typeof ToastService !== "undefined")
|
||||||
|
ToastService.showInfo("Quick Notes", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _formatAge(timestamp) {
|
||||||
|
if (!timestamp)
|
||||||
|
return "";
|
||||||
|
const seconds = Math.floor((Date.now() - timestamp) / 1000);
|
||||||
|
if (seconds < 60)
|
||||||
|
return "just now";
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
if (minutes < 60)
|
||||||
|
return minutes + "m ago";
|
||||||
|
const hours = Math.floor(minutes / 60);
|
||||||
|
if (hours < 24)
|
||||||
|
return hours + "h ago";
|
||||||
|
const days = Math.floor(hours / 24);
|
||||||
|
return days + "d ago";
|
||||||
|
}
|
||||||
|
|
||||||
|
onTriggerChanged: {
|
||||||
|
if (!pluginService)
|
||||||
|
return;
|
||||||
|
pluginService.savePluginData("quickNotesExample", "trigger", trigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
263
quickshell/PLUGINS/QuickNotesExample/QuickNotesSettings.qml
Normal file
263
quickshell/PLUGINS/QuickNotesExample/QuickNotesSettings.qml
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
FocusScope {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var pluginService: null
|
||||||
|
|
||||||
|
implicitHeight: settingsColumn.implicitHeight
|
||||||
|
height: implicitHeight
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: settingsColumn
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 16
|
||||||
|
spacing: 16
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Quick Notes Settings"
|
||||||
|
font.pixelSize: 18
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: "#FFFFFF"
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Demonstrates the plugin state API — notes are stored in a separate state file (quickNotesExample_state.json) rather than plugin_settings.json."
|
||||||
|
font.pixelSize: 14
|
||||||
|
color: "#CCFFFFFF"
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width - 32
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width - 32
|
||||||
|
height: 1
|
||||||
|
color: "#30FFFFFF"
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: 12
|
||||||
|
width: parent.width - 32
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Trigger Configuration"
|
||||||
|
font.pixelSize: 16
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: "#FFFFFF"
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: 12
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Trigger:"
|
||||||
|
font.pixelSize: 14
|
||||||
|
color: "#FFFFFF"
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: triggerField
|
||||||
|
width: 100
|
||||||
|
height: 40
|
||||||
|
text: loadSettings("trigger", "n")
|
||||||
|
placeholderText: "n"
|
||||||
|
backgroundColor: "#30FFFFFF"
|
||||||
|
textColor: "#FFFFFF"
|
||||||
|
|
||||||
|
onTextEdited: {
|
||||||
|
saveSettings("trigger", text.trim() || "n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width - 32
|
||||||
|
height: 1
|
||||||
|
color: "#30FFFFFF"
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: 12
|
||||||
|
width: parent.width - 32
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Storage"
|
||||||
|
font.pixelSize: 16
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: "#FFFFFF"
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Max notes:"
|
||||||
|
font.pixelSize: 14
|
||||||
|
color: "#FFFFFF"
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: maxNotesField
|
||||||
|
width: 80
|
||||||
|
height: 40
|
||||||
|
text: loadSettings("maxNotes", 50).toString()
|
||||||
|
placeholderText: "50"
|
||||||
|
backgroundColor: "#30FFFFFF"
|
||||||
|
textColor: "#FFFFFF"
|
||||||
|
|
||||||
|
onTextEdited: {
|
||||||
|
const val = parseInt(text);
|
||||||
|
if (!isNaN(val) && val > 0)
|
||||||
|
saveSettings("maxNotes", val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: {
|
||||||
|
const count = loadState("notes", []).length;
|
||||||
|
return "Currently storing " + count + " note(s)";
|
||||||
|
}
|
||||||
|
font.pixelSize: 12
|
||||||
|
color: "#AAFFFFFF"
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: clearRow.implicitWidth + 24
|
||||||
|
height: clearRow.implicitHeight + 16
|
||||||
|
radius: 8
|
||||||
|
color: clearMouseArea.containsMouse ? "#40FF5252" : "#30FF5252"
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: clearRow
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "🗑"
|
||||||
|
font.pixelSize: 14
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Clear all notes"
|
||||||
|
font.pixelSize: 14
|
||||||
|
color: "#FF5252"
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: clearMouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (pluginService) {
|
||||||
|
pluginService.clearPluginState("quickNotesExample");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width - 32
|
||||||
|
height: 1
|
||||||
|
color: "#30FFFFFF"
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: 8
|
||||||
|
width: parent.width - 32
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "API Usage (for plugin developers):"
|
||||||
|
font.pixelSize: 14
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: "#FFFFFF"
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
spacing: 4
|
||||||
|
leftPadding: 16
|
||||||
|
bottomPadding: 24
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "• pluginService.savePluginState(id, key, value)"
|
||||||
|
font.pixelSize: 12
|
||||||
|
color: "#CCFFFFFF"
|
||||||
|
font.family: "monospace"
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: " Writes to ~/.local/state/.../id_state.json"
|
||||||
|
font.pixelSize: 11
|
||||||
|
color: "#AAFFFFFF"
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "• pluginService.loadPluginState(id, key, default)"
|
||||||
|
font.pixelSize: 12
|
||||||
|
color: "#CCFFFFFF"
|
||||||
|
font.family: "monospace"
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: " Reads from the per-plugin state file"
|
||||||
|
font.pixelSize: 11
|
||||||
|
color: "#AAFFFFFF"
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "• pluginService.clearPluginState(id)"
|
||||||
|
font.pixelSize: 12
|
||||||
|
color: "#CCFFFFFF"
|
||||||
|
font.family: "monospace"
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: " Clears all state for a plugin"
|
||||||
|
font.pixelSize: 11
|
||||||
|
color: "#AAFFFFFF"
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "• pluginService.removePluginStateKey(id, key)"
|
||||||
|
font.pixelSize: 12
|
||||||
|
color: "#CCFFFFFF"
|
||||||
|
font.family: "monospace"
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: " Removes a single key from plugin state"
|
||||||
|
font.pixelSize: 11
|
||||||
|
color: "#AAFFFFFF"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveSettings(key, value) {
|
||||||
|
if (pluginService)
|
||||||
|
pluginService.savePluginData("quickNotesExample", key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadSettings(key, defaultValue) {
|
||||||
|
if (pluginService)
|
||||||
|
return pluginService.loadPluginData("quickNotesExample", key, defaultValue);
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadState(key, defaultValue) {
|
||||||
|
if (pluginService)
|
||||||
|
return pluginService.loadPluginState("quickNotesExample", key, defaultValue);
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
quickshell/PLUGINS/QuickNotesExample/plugin.json
Normal file
17
quickshell/PLUGINS/QuickNotesExample/plugin.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"id": "quickNotesExample",
|
||||||
|
"name": "Quick Notes",
|
||||||
|
"description": "Example launcher plugin demonstrating the plugin state API for persistent data like history and notes",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "DankMaterialShell",
|
||||||
|
"icon": "sticky_note_2",
|
||||||
|
"type": "launcher",
|
||||||
|
"capabilities": ["clipboard"],
|
||||||
|
"component": "./QuickNotesLauncher.qml",
|
||||||
|
"settings": "./QuickNotesSettings.qml",
|
||||||
|
"trigger": "n",
|
||||||
|
"permissions": [
|
||||||
|
"settings_read",
|
||||||
|
"settings_write"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -33,10 +33,17 @@ Singleton {
|
|||||||
property var pluginInstances: ({})
|
property var pluginInstances: ({})
|
||||||
property var globalVars: ({})
|
property var globalVars: ({})
|
||||||
|
|
||||||
|
property var _stateCache: ({})
|
||||||
|
property var _stateLoaded: ({})
|
||||||
|
property var _stateWriters: ({})
|
||||||
|
property var _stateDirtyPlugins: ({})
|
||||||
|
property bool _stateDirCreated: false
|
||||||
|
|
||||||
signal pluginLoaded(string pluginId)
|
signal pluginLoaded(string pluginId)
|
||||||
signal pluginUnloaded(string pluginId)
|
signal pluginUnloaded(string pluginId)
|
||||||
signal pluginLoadFailed(string pluginId, string error)
|
signal pluginLoadFailed(string pluginId, string error)
|
||||||
signal pluginDataChanged(string pluginId)
|
signal pluginDataChanged(string pluginId)
|
||||||
|
signal pluginStateChanged(string pluginId)
|
||||||
signal pluginListUpdated
|
signal pluginListUpdated
|
||||||
signal globalVarChanged(string pluginId, string varName)
|
signal globalVarChanged(string pluginId, string varName)
|
||||||
signal requestLauncherUpdate(string pluginId)
|
signal requestLauncherUpdate(string pluginId)
|
||||||
@@ -48,6 +55,13 @@ Singleton {
|
|||||||
onTriggered: resyncAll()
|
onTriggered: resyncAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: _stateWriteTimer
|
||||||
|
interval: 150
|
||||||
|
repeat: false
|
||||||
|
onTriggered: root._flushDirtyStates()
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
userWatcher.folder = Paths.toFileUrl(root.pluginDirectory);
|
userWatcher.folder = Paths.toFileUrl(root.pluginDirectory);
|
||||||
systemWatcher.folder = Paths.toFileUrl(root.systemPluginDirectory);
|
systemWatcher.folder = Paths.toFileUrl(root.systemPluginDirectory);
|
||||||
@@ -374,6 +388,7 @@ Singleton {
|
|||||||
delete newLoaded[pluginId];
|
delete newLoaded[pluginId];
|
||||||
loadedPlugins = newLoaded;
|
loadedPlugins = newLoaded;
|
||||||
|
|
||||||
|
_cleanupPluginStateWriter(pluginId);
|
||||||
pluginUnloaded(pluginId);
|
pluginUnloaded(pluginId);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -603,6 +618,111 @@ Singleton {
|
|||||||
SettingsData.savePluginSettings();
|
SettingsData.savePluginSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPluginStatePath(pluginId) {
|
||||||
|
return Paths.strip(Paths.state) + "/plugins/" + pluginId + "_state.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadPluginState(pluginId, key, defaultValue) {
|
||||||
|
if (!_stateLoaded[pluginId])
|
||||||
|
_loadStateFromDisk(pluginId);
|
||||||
|
const state = _stateCache[pluginId];
|
||||||
|
if (!state)
|
||||||
|
return defaultValue;
|
||||||
|
return state[key] !== undefined ? state[key] : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function savePluginState(pluginId, key, value) {
|
||||||
|
if (!_stateLoaded[pluginId])
|
||||||
|
_loadStateFromDisk(pluginId);
|
||||||
|
if (!_stateCache[pluginId])
|
||||||
|
_stateCache[pluginId] = {};
|
||||||
|
_stateCache[pluginId][key] = value;
|
||||||
|
_stateDirtyPlugins[pluginId] = true;
|
||||||
|
_stateWriteTimer.restart();
|
||||||
|
pluginStateChanged(pluginId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearPluginState(pluginId) {
|
||||||
|
_stateCache[pluginId] = {};
|
||||||
|
_stateLoaded[pluginId] = true;
|
||||||
|
_flushStateToDisk(pluginId);
|
||||||
|
pluginStateChanged(pluginId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePluginStateKey(pluginId, key) {
|
||||||
|
if (!_stateCache[pluginId])
|
||||||
|
return;
|
||||||
|
delete _stateCache[pluginId][key];
|
||||||
|
_stateDirtyPlugins[pluginId] = true;
|
||||||
|
_stateWriteTimer.restart();
|
||||||
|
pluginStateChanged(pluginId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _ensureStateDir() {
|
||||||
|
if (_stateDirCreated)
|
||||||
|
return;
|
||||||
|
_stateDirCreated = true;
|
||||||
|
Paths.mkdir(Paths.state + "/plugins");
|
||||||
|
}
|
||||||
|
|
||||||
|
function _loadStateFromDisk(pluginId) {
|
||||||
|
_stateLoaded[pluginId] = true;
|
||||||
|
_ensureStateDir();
|
||||||
|
const path = getPluginStatePath(pluginId);
|
||||||
|
const escapedPath = path.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
||||||
|
try {
|
||||||
|
const qml = 'import QtQuick; import Quickshell.Io; FileView { path: "' + escapedPath + '"; blockLoading: true; blockWrites: true; atomicWrites: true }';
|
||||||
|
const fv = Qt.createQmlObject(qml, root, "sf_" + pluginId);
|
||||||
|
const raw = fv.text();
|
||||||
|
if (raw && raw.trim()) {
|
||||||
|
_stateCache[pluginId] = JSON.parse(raw);
|
||||||
|
} else {
|
||||||
|
_stateCache[pluginId] = {};
|
||||||
|
}
|
||||||
|
_stateWriters[pluginId] = fv;
|
||||||
|
} catch (e) {
|
||||||
|
_stateCache[pluginId] = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _flushStateToDisk(pluginId) {
|
||||||
|
_ensureStateDir();
|
||||||
|
const content = JSON.stringify(_stateCache[pluginId] || {}, null, 2);
|
||||||
|
if (_stateWriters[pluginId]) {
|
||||||
|
_stateWriters[pluginId].setText(content);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const path = getPluginStatePath(pluginId);
|
||||||
|
const escapedPath = path.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
||||||
|
try {
|
||||||
|
const qml = 'import QtQuick; import Quickshell.Io; FileView { path: "' + escapedPath + '"; blockWrites: true; atomicWrites: true }';
|
||||||
|
const fv = Qt.createQmlObject(qml, root, "sw_" + pluginId);
|
||||||
|
_stateWriters[pluginId] = fv;
|
||||||
|
fv.loaded.connect(function () {
|
||||||
|
fv.setText(content);
|
||||||
|
});
|
||||||
|
fv.loadFailed.connect(function () {
|
||||||
|
fv.setText(content);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("PluginService: Failed to write state for", pluginId, e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _flushDirtyStates() {
|
||||||
|
const dirty = _stateDirtyPlugins;
|
||||||
|
_stateDirtyPlugins = {};
|
||||||
|
for (const pluginId in dirty)
|
||||||
|
_flushStateToDisk(pluginId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _cleanupPluginStateWriter(pluginId) {
|
||||||
|
if (!_stateWriters[pluginId])
|
||||||
|
return;
|
||||||
|
_stateWriters[pluginId].destroy();
|
||||||
|
delete _stateWriters[pluginId];
|
||||||
|
}
|
||||||
|
|
||||||
function scanPlugins() {
|
function scanPlugins() {
|
||||||
const userUrl = Paths.toFileUrl(root.pluginDirectory);
|
const userUrl = Paths.toFileUrl(root.pluginDirectory);
|
||||||
const systemUrl = Paths.toFileUrl(root.systemPluginDirectory);
|
const systemUrl = Paths.toFileUrl(root.systemPluginDirectory);
|
||||||
|
|||||||
Reference in New Issue
Block a user