From 61484413d7b1b562a8ac66cc4e1f42918025e128 Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 26 Jul 2026 12:53:57 +0200 Subject: [PATCH 01/43] feat!: init plugin --- config-swap/README.md | 83 ++++++++++++++++++++++++++++ config-swap/config-swap-panel.luau | 23 ++++++++ config-swap/config-swap-widget.luau | 8 +++ config-swap/plugin.toml | 20 +++++++ config-swap/thumbnail.webp | Bin 0 -> 44736 bytes config-swap/translations/en.json | 2 + 6 files changed, 136 insertions(+) create mode 100644 config-swap/README.md create mode 100644 config-swap/config-swap-panel.luau create mode 100644 config-swap/config-swap-widget.luau create mode 100644 config-swap/plugin.toml create mode 100644 config-swap/thumbnail.webp create mode 100644 config-swap/translations/en.json diff --git a/config-swap/README.md b/config-swap/README.md new file mode 100644 index 00000000..48fde32c --- /dev/null +++ b/config-swap/README.md @@ -0,0 +1,83 @@ +# W Engine + +A [Noctalia](https://github.com/noctalia-dev/noctalia) v5 plugin for applying Wallpaper Engine wallpapers from Steam Workshop. + +## Dependencies + +Before installing this plugin, make sure you already have Wallpaper Engine installed from Steam. Without it, the plugin will not work. + +You need Steam itself to download wallpapers from the Workshop, plus the `linux-wallpaperengine` tool. + +You also need `kill` to stop duplicate wallpaper processes when changing wallpapers, and `setsid` to start the wallpaper process correctly. + +For more details, see the install section below. + +## Plugin + +| Field | Value | +| --- | --- | +| ID | `tadomika_ari/w-engine` | +| Entries | Bar widget: `w-engine-widget`; panel: `w-engine-panel`; Service: `start`| + +## Usage + +Add the `W Engine` widget from Noctalia's widget picker, then click it to open the panel. You can also open the panel directly or bind it in your compositor: + +```sh +noctalia msg panel-toggle tadomika_ari/w-engine:w-engine-panel +``` + +| Action | Effect | +| --- | --- | +| Left click on the bar glyph | Open or close the W Engine panel | +| Click a wallpaper entry in the panel | Apply that wallpaper | +| Select an output | Change the monitor where the wallpaper is displayed | + +## Settings + +Custom path: + +To define a custom path for Steam or the Workshop, you can add a specific path for W Engine. +You can create a `data.json` file in `$NOCTALIA_STATE_HOME` based on this example: + +```json +{ + "personnalPath": ["path1"] +} +``` + +Replace `path1` with your personal path or add other paths. The path is loaded after a reload or if you reopen the widget. + +## Requirements + +- noctalia ≥ 5.0.0 +- `linux-wallpaperengine` +- `kill` +- `pkill` +- `setsid` +- Wallpaper Engine +- Steam for Workshop access + +## Install + +Install **W Engine** from Noctalia's plugin store (*Settings → Plugins*), then add the widget to a bar from *Settings → Bar*. Plugin options live in *Settings → Plugins*. + +Next, install Wallpaper Engine from Steam: https://store.steampowered.com/app/431960/Wallpaper_Engine/ + +Then install `linux-wallpaperengine` from its project page: https://github.com/Almamu/linux-wallpaperengine + +Note that some Wallpaper Engine wallpapers may not work correctly on Linux, especially ones with advanced visual effects. + +If you use NixOS, be aware that the package version in nixpkgs may lag behind upstream. + +For local development, add your working copy as a path source instead +(`.luau` edits hot-reload): + +```sh +noctalia msg plugins source add dev path /path/to/plugins +noctalia msg plugins enable tadomika_ari/w-engine +``` + +## License + +MIT. \ No newline at end of file diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau new file mode 100644 index 00000000..09c181e1 --- /dev/null +++ b/config-swap/config-swap-panel.luau @@ -0,0 +1,23 @@ +--!nonstrict +-- Config Swap + +-- Local variable + +local function render() + panel.render(ui.column({ flexGrow = 1, gap = 16 }, { + ui.row({ align = "center", justify = "space_between", gap = 8 }, { + ui.label({ text = tr("panel.title"), align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.select({ options = {}, onChange = "" }), + ui.button({ glyph = "close", onClick = "onCloseClicked" }), + }), + ui.scroll({ gap = 12, align = "stretch", flexGrow = 1 }, rows), + })) +end + +function onOpen(_context) + render() +end + +function onCloseClicked() + panel.close() +end \ No newline at end of file diff --git a/config-swap/config-swap-widget.luau b/config-swap/config-swap-widget.luau new file mode 100644 index 00000000..019067e7 --- /dev/null +++ b/config-swap/config-swap-widget.luau @@ -0,0 +1,8 @@ +function update() + noctalia.setUpdateInterval(1000) + barWidget.setGlyph("file") +end + +function onClick() + noctalia.togglePanel("tadomika_ari/config-swap:panel") +end \ No newline at end of file diff --git a/config-swap/plugin.toml b/config-swap/plugin.toml new file mode 100644 index 00000000..465beb63 --- /dev/null +++ b/config-swap/plugin.toml @@ -0,0 +1,20 @@ +id = "tadomika_ari/config-swap" +name = "Config Swap" +version = "1.0.0" +plugin_api = 3 +author = "TadomiKa-Ari" +license = "MIT" +dependencies = [""] +icon = "file" +description = "A Noctalia widget for applying Wallpaper Engine wallpapers from Steam Workshop with one click" +tags = ["utility"] + +[[widget]] +id = "widget" +entry = "w-engine-widget.luau" + +[[panel]] +id = "panel" +entry = "config-swap-panel.luau" +width = 800 +height = 600 \ No newline at end of file diff --git a/config-swap/thumbnail.webp b/config-swap/thumbnail.webp new file mode 100644 index 0000000000000000000000000000000000000000..14ef8b4c72ea1c0a16f2438db535b1c27b9c195d GIT binary patch literal 44736 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 Date: Sun, 26 Jul 2026 13:51:06 +0200 Subject: [PATCH 02/43] fix: fix widget not found on bar setting --- config-swap/plugin.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-swap/plugin.toml b/config-swap/plugin.toml index 465beb63..a09b312a 100644 --- a/config-swap/plugin.toml +++ b/config-swap/plugin.toml @@ -11,7 +11,7 @@ tags = ["utility"] [[widget]] id = "widget" -entry = "w-engine-widget.luau" +entry = "config-swap-widget.luau" [[panel]] id = "panel" From 7a61778d75aaf7bd6851bfa02e9ac98587b71fb5 Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 26 Jul 2026 23:06:20 +0200 Subject: [PATCH 03/43] feat!: add creation folder for plugin --- config-swap/config-swap-service.luau | 1 + 1 file changed, 1 insertion(+) create mode 100644 config-swap/config-swap-service.luau diff --git a/config-swap/config-swap-service.luau b/config-swap/config-swap-service.luau new file mode 100644 index 00000000..ee8a9f76 --- /dev/null +++ b/config-swap/config-swap-service.luau @@ -0,0 +1 @@ +noctalia.mkdirAll("~/Config-Swap") \ No newline at end of file From a2950d06c06fe98e9d7470860a1e8ee050d53cdb Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 26 Jul 2026 23:06:54 +0200 Subject: [PATCH 04/43] feat!: add first version of Config Swap (simple modification test) --- config-swap/config-swap-panel.luau | 137 ++++++++++++++++++++++++++++- 1 file changed, 135 insertions(+), 2 deletions(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index 09c181e1..ea9ff344 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -3,17 +3,150 @@ -- Local variable +local urlGithub = "https://github.com/Tadomika-Ari/Config-Swap-Box" +local urlPath = "https://api.github.com/repos/Tadomika-Ari/Config-Swap-Box/contents/" -- need add path + +local configBox = "~/Config-Swap" + +local listConfig = nil +local configUse = nil +local allConfig = nil +local optionFull = {"all", "not full", "full"} + + +local pickSlots = {} +local columns = 2 +local previewWidth = 375 +local previewHeight = 250 +local previewImage = "~/Downloads/arme.png" + +-- Type function + +type config = { + name: string, + path: string, + full: boolean, +} + +-- test section + +local allConfig: { config } = {} + +local requestFetchList = { + url = "https://api.github.com/repos/Tadomika-Ari/Config-Swap-Box/contents/config", + method = "GET", + headers = { "Accept: application/json", "User-Agent: Config-Swap-Box-Plugin" }, + follow_redirects = true +} + +-- + +function applyConfig(data: config) + noctalia.notify("Apply") + + local cmd = "cp " .. configBox .. "/" .. data.name .. "/settings.toml " .. "~/.local/state/noctalia/settings.toml" + + noctalia.runAsync(cmd) +end + +function downloadConfig(data: config) + noctalia.notify("Install") + + noctalia.mkdirAll(configBox .. "/" .. data.name) + + local path = data.path .. "/settings.toml" + local url = "https://raw.githubusercontent.com/Tadomika-Ari/Config-Swap-Box/main/" .. path + noctalia.download(url, configBox .. "/" .. data.name .. "/settings.toml" , function(ok) + if ok then + noctalia.notify("Config Swap", "Download ok") + else + noctalia.notify("Config Swap", "Download not ok") + end + end) +end + local function render() + + pickSlots = {} + local tiles = {} + local list = listConfig or {} + for count = 1, #list, 1 do + local data = list[count] + local slotIndex = count - 1 + pickSlots[slotIndex] = data.path + table.insert(tiles, ui.column({ gap = 4, key = data.path, align = "center" }, { + ui.image({ + path = previewImage, + width = previewWidth, + height = previewHeight, + fit = "cover", + radius = 8, + onClick = "onCloseClicked" + }), + ui.row( {gap = 12, align = "start"}, { + ui.button({ text = "install", onClick = function() + downloadConfig(data) + end + }), + ui.label({ + text = data.name, + fontSize = 11, + maxLines = 1, + maxWidth = 120, + textAlign = "center", + }), + ui.button({ text = "apply", onClick = function() + applyConfig(data) + end + }), + } ), + })) + end + local rows = {} + local row = {} + for i, tile in ipairs(tiles) do + table.insert(row, tile) + if #row == columns or i == #tiles then + table.insert(rows, ui.row({ gap = 12, align = "start" }, row)) + row = {} + end + end + panel.render(ui.column({ flexGrow = 1, gap = 16 }, { ui.row({ align = "center", justify = "space_between", gap = 8 }, { - ui.label({ text = tr("panel.title"), align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), - ui.select({ options = {}, onChange = "" }), + ui.label({ text = "Config Swap", align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.button({ text = "download", onClick = "downloadConfig"}), + ui.select({ options = optionFull, onChange = "" }), + ui.button({ text = "refresh", onClick = "getFetchList"}), ui.button({ glyph = "close", onClick = "onCloseClicked" }), }), ui.scroll({ gap = 12, align = "stretch", flexGrow = 1 }, rows), })) end +function getFetchList(onDone) + noctalia.http(requestFetchList, function(respond) + noctalia.log("raw body: " .. tostring(respond.body)) + if not respond.ok then + noctalia.notify("Error", respond.status) + end + local data, err = noctalia.json.decode(respond.body) + if err then + noctalia.log(err) + return + end + for i, item in ipairs(data) do + table.insert(allConfig, { + name = item.name, + path = item.path, + full = false + }) + end + listConfig = data + render() + end) +end + function onOpen(_context) render() end From 5496b1f01157228ab082bfeab4e28ac8b810ab19 Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 26 Jul 2026 23:07:25 +0200 Subject: [PATCH 05/43] feat!: update toml with new feature --- config-swap/plugin.toml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/config-swap/plugin.toml b/config-swap/plugin.toml index a09b312a..252c6209 100644 --- a/config-swap/plugin.toml +++ b/config-swap/plugin.toml @@ -1,7 +1,7 @@ id = "tadomika_ari/config-swap" name = "Config Swap" version = "1.0.0" -plugin_api = 3 +plugin_api = 4 author = "TadomiKa-Ari" license = "MIT" dependencies = [""] @@ -17,4 +17,8 @@ entry = "config-swap-widget.luau" id = "panel" entry = "config-swap-panel.luau" width = 800 -height = 600 \ No newline at end of file +height = 600 + +[[service]] +id = "start" +entry = "config-swap-service.luau" \ No newline at end of file From 9ba98949ad73e1abe5f9c5440c28dd4d0c37ad0e Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 26 Jul 2026 23:07:41 +0200 Subject: [PATCH 06/43] docs: prepare docs --- config-swap/README.md | 53 +++++++------------------------------------ 1 file changed, 8 insertions(+), 45 deletions(-) diff --git a/config-swap/README.md b/config-swap/README.md index 48fde32c..73ce870b 100644 --- a/config-swap/README.md +++ b/config-swap/README.md @@ -4,40 +4,23 @@ A [Noctalia](https://github.com/noctalia-dev/noctalia) v5 plugin for applying Wa ## Dependencies -Before installing this plugin, make sure you already have Wallpaper Engine installed from Steam. Without it, the plugin will not work. - -You need Steam itself to download wallpapers from the Workshop, plus the `linux-wallpaperengine` tool. - -You also need `kill` to stop duplicate wallpaper processes when changing wallpapers, and `setsid` to start the wallpaper process correctly. - -For more details, see the install section below. +Comming soon ## Plugin | Field | Value | | --- | --- | -| ID | `tadomika_ari/w-engine` | -| Entries | Bar widget: `w-engine-widget`; panel: `w-engine-panel`; Service: `start`| +| ID | `tadomika_ari/config-swap` | +| Entries | Bar widget: `config-swap-widget`; panel: `config-swap-panel`; Service: `start`| ## Usage -Add the `W Engine` widget from Noctalia's widget picker, then click it to open the panel. You can also open the panel directly or bind it in your compositor: - -```sh -noctalia msg panel-toggle tadomika_ari/w-engine:w-engine-panel -``` - -| Action | Effect | -| --- | --- | -| Left click on the bar glyph | Open or close the W Engine panel | -| Click a wallpaper entry in the panel | Apply that wallpaper | -| Select an output | Change the monitor where the wallpaper is displayed | +Comming soon ## Settings -Custom path: +Custom path (Support Soon) : -To define a custom path for Steam or the Workshop, you can add a specific path for W Engine. You can create a `data.json` file in `$NOCTALIA_STATE_HOME` based on this example: ```json @@ -51,32 +34,12 @@ Replace `path1` with your personal path or add other paths. The path is loaded a ## Requirements - noctalia ≥ 5.0.0 -- `linux-wallpaperengine` -- `kill` -- `pkill` -- `setsid` -- Wallpaper Engine -- Steam for Workshop access -## Install +Next comming soon -Install **W Engine** from Noctalia's plugin store (*Settings → Plugins*), then add the widget to a bar from *Settings → Bar*. Plugin options live in *Settings → Plugins*. - -Next, install Wallpaper Engine from Steam: https://store.steampowered.com/app/431960/Wallpaper_Engine/ - -Then install `linux-wallpaperengine` from its project page: https://github.com/Almamu/linux-wallpaperengine - -Note that some Wallpaper Engine wallpapers may not work correctly on Linux, especially ones with advanced visual effects. - -If you use NixOS, be aware that the package version in nixpkgs may lag behind upstream. - -For local development, add your working copy as a path source instead -(`.luau` edits hot-reload): +## Install -```sh -noctalia msg plugins source add dev path /path/to/plugins -noctalia msg plugins enable tadomika_ari/w-engine -``` +Comming soon ## License From 99db16f665dd10c67c92858968b73279ab8d16a8 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Jul 2026 15:02:40 +0200 Subject: [PATCH 07/43] feat!: add more place for view preview --- config-swap/plugin.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config-swap/plugin.toml b/config-swap/plugin.toml index 252c6209..11503ddc 100644 --- a/config-swap/plugin.toml +++ b/config-swap/plugin.toml @@ -16,8 +16,8 @@ entry = "config-swap-widget.luau" [[panel]] id = "panel" entry = "config-swap-panel.luau" -width = 800 -height = 600 +width = 1000 +height = 800 [[service]] id = "start" From 1e212cf57876b2d44b04921a80ecdfe8f10fc393 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Jul 2026 15:04:18 +0200 Subject: [PATCH 08/43] feat!: add new page for store and allerady download --- config-swap/config-swap-panel.luau | 192 ++++++++++++++++++++++++++++- 1 file changed, 187 insertions(+), 5 deletions(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index ea9ff344..375b31f2 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -16,10 +16,15 @@ local optionFull = {"all", "not full", "full"} local pickSlots = {} local columns = 2 -local previewWidth = 375 -local previewHeight = 250 +local previewWidth = 470 +local previewHeight = 350 local previewImage = "~/Downloads/arme.png" +local selectedView = 1 + +local previewCache = {} +local previewPending = {} + -- Type function type config = { @@ -28,6 +33,15 @@ type config = { full: boolean, } +type infoConfig = { + name: string, + author: string, + origin: string, + preview: string, + wallpaperPath: string, + depedencie: boolean, +} + -- test section local allConfig: { config } = {} @@ -41,6 +55,10 @@ local requestFetchList = { -- +function test() + noctalia.notify(noctalia.wallpaperDirectory()) +end + function applyConfig(data: config) noctalia.notify("Apply") @@ -63,20 +81,73 @@ function downloadConfig(data: config) noctalia.notify("Config Swap", "Download not ok") end end) + local path = data.path .. "/info.json" + local url = "https://raw.githubusercontent.com/Tadomika-Ari/Config-Swap-Box/main/" .. path + noctalia.download(url, configBox .. "/" .. data.name .. "/info.json" , function(ok) + if ok then + noctalia.notify("Config Swap", "Download ok") + else + noctalia.notify("Config Swap", "Download not ok") + end + end) + local path = data.path .. "/preview.png" + local url = "https://raw.githubusercontent.com/Tadomika-Ari/Config-Swap-Box/main/" .. path + noctalia.download(url, configBox .. "/" .. data.name .. "/preview.png" , function(ok) + if ok then + noctalia.notify("Config Swap", "Download ok") + else + noctalia.notify("Config Swap", "Download not ok") + end + end) end -local function render() +local function previewCachePath(name) + local dir = noctalia.pluginDataDir() .. "/preview-cache" + noctalia.mkdirAll(dir) + return dir .. "/" .. name .. ".png" +end + +local function queuePreviewDownload(name) + if previewCache[name] ~= nil or previewPending[name] then + return + end + + local dest = previewCachePath(name) + if noctalia.fileExists(dest) then + previewCache[name] = dest + return + end + + previewPending[name] = true + local url = "https://raw.githubusercontent.com/Tadomika-Ari/Config-Swap-Box/main/config/" .. name .. "/preview.png" + + noctalia.download(url, dest, function(ok) + previewPending[name] = nil + if ok then + previewCache[name] = dest + render() + end + end) +end + +local function renderList() pickSlots = {} local tiles = {} local list = listConfig or {} + for count = 1, #list, 1 do local data = list[count] local slotIndex = count - 1 pickSlots[slotIndex] = data.path + + queuePreviewDownload(data.name) + + local imagePath = previewCache[data.name] or previewImage + table.insert(tiles, ui.column({ gap = 4, key = data.path, align = "center" }, { ui.image({ - path = previewImage, + path = imagePath, width = previewWidth, height = previewHeight, fit = "cover", @@ -115,7 +186,12 @@ local function render() panel.render(ui.column({ flexGrow = 1, gap = 16 }, { ui.row({ align = "center", justify = "space_between", gap = 8 }, { ui.label({ text = "Config Swap", align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), - ui.button({ text = "download", onClick = "downloadConfig"}), + ui.label({ text = "Config Store", align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.button({ text = "Show List", onClick = function() + selectedView = 1 + render() + end + }), ui.select({ options = optionFull, onChange = "" }), ui.button({ text = "refresh", onClick = "getFetchList"}), ui.button({ glyph = "close", onClick = "onCloseClicked" }), @@ -124,6 +200,112 @@ local function render() })) end + +function getListInstall() + local localPath = configBox + local list = noctalia.listDir(localPath) + local listShow: { infoConfig } = {} + + for i, entryName in ipairs(list) do + local infoPath = configBox .. "/" .. entryName .. "/info.json" + local content = noctalia.readFile(infoPath) + local data = content and noctalia.json.decode(content) or nil + + if not data then + table.insert(listShow, { + name = entryName, + author = "None", + origin = "None", + preview = nil, + wallpaperPath = nil, + depedencie = false, + }) + continue + end + + table.insert(listShow, { + name = data.name, + author = data.author, + origin = data.origin, + preview = data.preview, + wallpaperPath = data.wallpaperPath, + depedencie = data.depedencie, + }) + end + return listShow +end + +local function renderInstall() + pickSlots = {} + local tiles = {} + local list = getListInstall() or {} + for count = 1, #list, 1 do + local data = list[count] + local slotIndex = count - 1 + pickSlots[slotIndex] = data.path + table.insert(tiles, ui.column({ gap = 4, key = data.path, align = "center" }, { + ui.image({ + path = configBox .. "/" .. data.name .. "/preview.png", + width = previewWidth, + height = previewHeight, + fit = "cover", + radius = 8, + onClick = "onCloseClicked" + }), + ui.row( {gap = 12, align = "start"}, { + ui.button({ text = "install", onClick = function() + downloadConfig(data) + end + }), + ui.label({ + text = data.name, + fontSize = 11, + maxLines = 1, + maxWidth = 120, + textAlign = "center", + }), + ui.button({ text = "apply", onClick = function() + applyConfig(data) + end + }), + } ), + })) + end + local rows = {} + local row = {} + for i, tile in ipairs(tiles) do + table.insert(row, tile) + if #row == columns or i == #tiles then + table.insert(rows, ui.row({ gap = 12, align = "start" }, row)) + row = {} + end + end + panel.render(ui.column({ flexGrow = 1, gap = 16 }, { + ui.row({ align = "center", justify = "space_between", gap = 8 }, { + ui.label({ text = "Config Swap", align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.label({ text = "Installed config", align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.button({ text = "Show Store", onClick = function() + selectedView = 2 + render() + end + }), + ui.select({ options = optionFull, onChange = "" }), + ui.button({ text = "refresh", onClick = "getFetchList"}), + ui.button({ glyph = "close", onClick = "onCloseClicked" }), + }), + ui.scroll({ gap = 12, align = "stretch", flexGrow = 1 }, rows), + })) +end + +function render() + if selectedView == 1 then + renderInstall() + end + if selectedView == 2 then + renderList() + end +end + function getFetchList(onDone) noctalia.http(requestFetchList, function(respond) noctalia.log("raw body: " .. tostring(respond.body)) From 8b410f2ce8aadbe827dcc24034dcc7619b44b43b Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Jul 2026 16:23:05 +0200 Subject: [PATCH 09/43] docs: improve lisibility --- config-swap/config-swap-panel.luau | 47 +++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index 375b31f2..9f73284e 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -3,17 +3,11 @@ -- Local variable -local urlGithub = "https://github.com/Tadomika-Ari/Config-Swap-Box" -local urlPath = "https://api.github.com/repos/Tadomika-Ari/Config-Swap-Box/contents/" -- need add path - -local configBox = "~/Config-Swap" +local configBox = "~/Config-Swap" -- path for storage local listConfig = nil -local configUse = nil -local allConfig = nil local optionFull = {"all", "not full", "full"} - local pickSlots = {} local columns = 2 local previewWidth = 470 @@ -25,6 +19,9 @@ local selectedView = 1 local previewCache = {} local previewPending = {} +local configWidth = 800 +local configHeight = 600 + -- Type function type config = { @@ -59,6 +56,8 @@ function test() noctalia.notify(noctalia.wallpaperDirectory()) end +-- apply config file + function applyConfig(data: config) noctalia.notify("Apply") @@ -67,6 +66,8 @@ function applyConfig(data: config) noctalia.runAsync(cmd) end +-- download config folder (support: noctalia settings, preview image and json info + function downloadConfig(data: config) noctalia.notify("Install") @@ -101,6 +102,8 @@ function downloadConfig(data: config) end) end +-- Section Preview for github store + local function previewCachePath(name) local dir = noctalia.pluginDataDir() .. "/preview-cache" noctalia.mkdirAll(dir) @@ -130,6 +133,8 @@ local function queuePreviewDownload(name) end) end +-- Section render for github Store + local function renderList() pickSlots = {} @@ -200,6 +205,24 @@ local function renderList() })) end +-- Section render for see config install info + +local function renderConfig(data: infoConfig) + panel.render(ui.column({ flexGrow = 1, gap = 16 }, { + ui.image({ + path = configBox .. "/" .. data.name .. "/preview.png", + width = configWidth, + height = configHeight, + fit = "cover", + radius = 8, + }), + ui.label({ text = data.name, align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.label({ text = "Create by " .. data.author, align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.button({ glyph = "close", onClick = "onCloseClicked" }), + })) +end + +-- Section for list allerady install file function getListInstall() local localPath = configBox @@ -250,7 +273,9 @@ local function renderInstall() height = previewHeight, fit = "cover", radius = 8, - onClick = "onCloseClicked" + onClick = function() + renderConfig(data) + end }), ui.row( {gap = 12, align = "start"}, { ui.button({ text = "install", onClick = function() @@ -297,6 +322,8 @@ local function renderInstall() })) end +-- Render Choice + function render() if selectedView == 1 then renderInstall() @@ -306,6 +333,8 @@ function render() end end +-- Get all config on github + function getFetchList(onDone) noctalia.http(requestFetchList, function(respond) noctalia.log("raw body: " .. tostring(respond.body)) @@ -329,6 +358,8 @@ function getFetchList(onDone) end) end +-- Other function + function onOpen(_context) render() end From bbdc35f078eb3c0619d069e5ce9c83ae443c90b1 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Jul 2026 16:57:00 +0200 Subject: [PATCH 10/43] feat!: add description emplacement --- config-swap/config-swap-panel.luau | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index 9f73284e..bbfa5bbc 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -35,6 +35,7 @@ type infoConfig = { author: string, origin: string, preview: string, + description: string, wallpaperPath: string, depedencie: boolean, } @@ -218,7 +219,9 @@ local function renderConfig(data: infoConfig) }), ui.label({ text = data.name, align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), ui.label({ text = "Create by " .. data.author, align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), - ui.button({ glyph = "close", onClick = "onCloseClicked" }), + ui.label({ text = "Origin : " .. data.origin, align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.label({ text = "Description : " .. data.description, align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.button({ glyph = "close", onClick = "render" }), })) end @@ -240,6 +243,7 @@ function getListInstall() author = "None", origin = "None", preview = nil, + description = nil, wallpaperPath = nil, depedencie = false, }) @@ -251,6 +255,7 @@ function getListInstall() author = data.author, origin = data.origin, preview = data.preview, + description = data.description, wallpaperPath = data.wallpaperPath, depedencie = data.depedencie, }) From 7466ac7eab5e44c1173ee4b5188ea36b2cf661ba Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Jul 2026 17:56:07 +0200 Subject: [PATCH 11/43] feat!: add wallpaper support for install and set --- config-swap/config-swap-panel.luau | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index bbfa5bbc..6f1b009d 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -63,6 +63,7 @@ function applyConfig(data: config) noctalia.notify("Apply") local cmd = "cp " .. configBox .. "/" .. data.name .. "/settings.toml " .. "~/.local/state/noctalia/settings.toml" + noctalia.setWallpaper(configBox .. "/" .. data.name .. "/wallpaper.png") noctalia.runAsync(cmd) end @@ -101,6 +102,15 @@ function downloadConfig(data: config) noctalia.notify("Config Swap", "Download not ok") end end) + local path = data.path .. "/wallpaper.png" + local url = "https://raw.githubusercontent.com/Tadomika-Ari/Config-Swap-Box/main/" .. path + noctalia.download(url, configBox .. "/" .. data.name .. "/wallpaper.png" , function(ok) + if ok then + noctalia.notify("Config Swap", "Download ok") + else + noctalia.notify("Config Swap", "Download not ok") + end + end) end -- Section Preview for github store From 8e94e468c32dd8fbb859024550fab7338fe783b9 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Jul 2026 18:40:40 +0200 Subject: [PATCH 12/43] docs: add more info to readme --- config-swap/README.md | 62 ++++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/config-swap/README.md b/config-swap/README.md index 73ce870b..5b566c19 100644 --- a/config-swap/README.md +++ b/config-swap/README.md @@ -1,45 +1,71 @@ -# W Engine +# Config Swap -A [Noctalia](https://github.com/noctalia-dev/noctalia) v5 plugin for applying Wallpaper Engine wallpapers from Steam Workshop. +A [Noctalia](https://github.com/noctalia-dev/noctalia) v5 plugin for switching between saved Noctalia configurations. ## Dependencies -Comming soon +For this plugin, you need: +* internet access to fetch configuration data from GitHub on the store page +* `cp` to apply a configuration ## Plugin | Field | Value | | --- | --- | | ID | `tadomika_ari/config-swap` | -| Entries | Bar widget: `config-swap-widget`; panel: `config-swap-panel`; Service: `start`| +| Entries | Bar widget: `config-swap-widget`; panel: `config-swap-panel`; service: `start` | ## Usage -Comming soon +This plugin is still under development. -## Settings +The plugin downloads saved configurations from the `Config-Swap-Box` GitHub repository and stores them in `~/Config-Swap`. +Applying a configuration copies its `settings.toml` into `~/.local/state/noctalia/settings.toml` and sets the matching wallpaper. +Keep a backup of your current configuration before using it. -Custom path (Support Soon) : +Open the panel from the bar widget, then switch between the two views with `Show Store` and `Show List`. -You can create a `data.json` file in `$NOCTALIA_STATE_HOME` based on this example: +### Show Store -```json -{ - "personnalPath": ["path1"] -} -``` +The store view fetches available configurations from GitHub. +Use `refresh` to reload the list if nothing appears. -Replace `path1` with your personal path or add other paths. The path is loaded after a reload or if you reopen the widget. +Do not spam `refresh`; GitHub rate limits unauthenticated requests. -## Requirements +Each card lets you `install` or `apply` a configuration. + +### Show List + +The list view shows configurations already installed in `~/Config-Swap`. +From there you can `apply` them again or `install` them again to refresh the files. + +Click a preview image to see extra information such as the author, origin, and description. + +Use `Show Store` to return to the GitHub store view. + +### Extra Info + +You can add your own configuration folder to `~/Config-Swap`. +Each configuration should follow the same structure as the downloaded ones, including an `info.json` file and the expected asset files. -- noctalia ≥ 5.0.0 +If you want to publish a configuration, add it to the `Config-Swap-Box` repository: https://github.com/Tadomika-Ari/Config-Swap-Box +Make sure Config Swap is enabled in your plugin list before you try to use it. + + + +## Settings + +There are no configurable settings yet. + +## Requirements -Next comming soon +- Noctalia ≥ 5.0.0 +- `cp` +- internet access for the store view ## Install -Comming soon +Install the plugin and add it to your bar. ## License From 7ca23d08097333472272c5270e54eb616ce79b10 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Jul 2026 18:40:56 +0200 Subject: [PATCH 13/43] feat: add cp depedencie --- config-swap/plugin.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-swap/plugin.toml b/config-swap/plugin.toml index 11503ddc..831154d0 100644 --- a/config-swap/plugin.toml +++ b/config-swap/plugin.toml @@ -4,7 +4,7 @@ version = "1.0.0" plugin_api = 4 author = "TadomiKa-Ari" license = "MIT" -dependencies = [""] +dependencies = ["cp"] icon = "file" description = "A Noctalia widget for applying Wallpaper Engine wallpapers from Steam Workshop with one click" tags = ["utility"] From 20f5adf082605ab4532c732916eed439a5898fa1 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Jul 2026 18:52:42 +0200 Subject: [PATCH 14/43] docs: add more info --- config-swap/README.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/config-swap/README.md b/config-swap/README.md index 5b566c19..7e03197e 100644 --- a/config-swap/README.md +++ b/config-swap/README.md @@ -13,12 +13,18 @@ For this plugin, you need: | Field | Value | | --- | --- | | ID | `tadomika_ari/config-swap` | -| Entries | Bar widget: `config-swap-widget`; panel: `config-swap-panel`; service: `start` | +| Entries | Bar widget: `widget`; panel: `panel`; service: `start` | ## Usage This plugin is still under development. +You can open Config Swap with: + +```sh +noctalia msg panel-toggle tadomika_ari/config-swap:panel +``` + The plugin downloads saved configurations from the `Config-Swap-Box` GitHub repository and stores them in `~/Config-Swap`. Applying a configuration copies its `settings.toml` into `~/.local/state/noctalia/settings.toml` and sets the matching wallpaper. Keep a backup of your current configuration before using it. @@ -34,6 +40,14 @@ Do not spam `refresh`; GitHub rate limits unauthenticated requests. Each card lets you `install` or `apply` a configuration. + +| Action | Effect | +| --- | --- | +| Refresh | Refresh Github API | +| Install | Install the config in `~/Config-Swap` | +| Apply | Apply Config file | +| Show list | Switch to list | + ### Show List The list view shows configurations already installed in `~/Config-Swap`. @@ -43,6 +57,16 @@ Click a preview image to see extra information such as the author, origin, and d Use `Show Store` to return to the GitHub store view. +| Action | Effect | +| --- | --- | +| Refresh | Refresh Github API | +| Install | Install the config in `~/Config-Swap` | +| Apply | Apply Config file | +| Click the preview | Open extra info | +| Show store | Switch to store | + + + ### Extra Info You can add your own configuration folder to `~/Config-Swap`. From 2664511d1343ef8e84f59a60a1ab051088b07bd7 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Jul 2026 19:17:50 +0200 Subject: [PATCH 15/43] feat!: add trad support --- config-swap/config-swap-panel.luau | 55 ++++++++++++++++-------------- config-swap/translations/en.json | 18 ++++++++++ 2 files changed, 47 insertions(+), 26 deletions(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index 6f1b009d..72816085 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -40,7 +40,7 @@ type infoConfig = { depedencie: boolean, } --- test section +-- section local allConfig: { config } = {} @@ -53,14 +53,17 @@ local requestFetchList = { -- -function test() - noctalia.notify(noctalia.wallpaperDirectory()) +function tr(key, subst) + if subst then + return noctalia.tr("panel." .. key, subst) + end + return noctalia.tr("panel." .. key) end -- apply config file function applyConfig(data: config) - noctalia.notify("Apply") + noctalia.notify(tr("apply_notification_title")) local cmd = "cp " .. configBox .. "/" .. data.name .. "/settings.toml " .. "~/.local/state/noctalia/settings.toml" noctalia.setWallpaper(configBox .. "/" .. data.name .. "/wallpaper.png") @@ -71,7 +74,7 @@ end -- download config folder (support: noctalia settings, preview image and json info function downloadConfig(data: config) - noctalia.notify("Install") + noctalia.notify(tr("install_notification_title")) noctalia.mkdirAll(configBox .. "/" .. data.name) @@ -79,36 +82,36 @@ function downloadConfig(data: config) local url = "https://raw.githubusercontent.com/Tadomika-Ari/Config-Swap-Box/main/" .. path noctalia.download(url, configBox .. "/" .. data.name .. "/settings.toml" , function(ok) if ok then - noctalia.notify("Config Swap", "Download ok") + noctalia.notify(tr("notification_title"), tr("download_ok")) else - noctalia.notify("Config Swap", "Download not ok") + noctalia.notify(tr("notification_title"), tr("download_failed")) end end) local path = data.path .. "/info.json" local url = "https://raw.githubusercontent.com/Tadomika-Ari/Config-Swap-Box/main/" .. path noctalia.download(url, configBox .. "/" .. data.name .. "/info.json" , function(ok) if ok then - noctalia.notify("Config Swap", "Download ok") + noctalia.notify(tr("notification_title"), tr("download_ok")) else - noctalia.notify("Config Swap", "Download not ok") + noctalia.notify(tr("notification_title"), tr("download_failed")) end end) local path = data.path .. "/preview.png" local url = "https://raw.githubusercontent.com/Tadomika-Ari/Config-Swap-Box/main/" .. path noctalia.download(url, configBox .. "/" .. data.name .. "/preview.png" , function(ok) if ok then - noctalia.notify("Config Swap", "Download ok") + noctalia.notify(tr("notification_title"), tr("download_ok")) else - noctalia.notify("Config Swap", "Download not ok") + noctalia.notify(tr("notification_title"), tr("download_failed")) end end) local path = data.path .. "/wallpaper.png" local url = "https://raw.githubusercontent.com/Tadomika-Ari/Config-Swap-Box/main/" .. path noctalia.download(url, configBox .. "/" .. data.name .. "/wallpaper.png" , function(ok) if ok then - noctalia.notify("Config Swap", "Download ok") + noctalia.notify(tr("notification_title"), tr("download_ok")) else - noctalia.notify("Config Swap", "Download not ok") + noctalia.notify(tr("notification_title"), tr("download_failed")) end end) end @@ -171,7 +174,7 @@ local function renderList() onClick = "onCloseClicked" }), ui.row( {gap = 12, align = "start"}, { - ui.button({ text = "install", onClick = function() + ui.button({ text = tr("install"), onClick = function() downloadConfig(data) end }), @@ -182,7 +185,7 @@ local function renderList() maxWidth = 120, textAlign = "center", }), - ui.button({ text = "apply", onClick = function() + ui.button({ text = tr("apply"), onClick = function() applyConfig(data) end }), @@ -201,15 +204,15 @@ local function renderList() panel.render(ui.column({ flexGrow = 1, gap = 16 }, { ui.row({ align = "center", justify = "space_between", gap = 8 }, { - ui.label({ text = "Config Swap", align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), - ui.label({ text = "Config Store", align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), - ui.button({ text = "Show List", onClick = function() + ui.label({ text = tr("title"), align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.label({ text = tr("store_title"), align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.button({ text = tr("show_list"), onClick = function() selectedView = 1 render() end }), ui.select({ options = optionFull, onChange = "" }), - ui.button({ text = "refresh", onClick = "getFetchList"}), + ui.button({ text = tr("refresh"), onClick = "getFetchList"}), ui.button({ glyph = "close", onClick = "onCloseClicked" }), }), ui.scroll({ gap = 12, align = "stretch", flexGrow = 1 }, rows), @@ -228,9 +231,9 @@ local function renderConfig(data: infoConfig) radius = 8, }), ui.label({ text = data.name, align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), - ui.label({ text = "Create by " .. data.author, align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), - ui.label({ text = "Origin : " .. data.origin, align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), - ui.label({ text = "Description : " .. data.description, align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.label({ text = tr("created_by", { author = data.author }), align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.label({ text = tr("origin", { origin = data.origin }), align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.label({ text = tr("description", { description = data.description or "" }), align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), ui.button({ glyph = "close", onClick = "render" }), })) end @@ -322,15 +325,15 @@ local function renderInstall() end panel.render(ui.column({ flexGrow = 1, gap = 16 }, { ui.row({ align = "center", justify = "space_between", gap = 8 }, { - ui.label({ text = "Config Swap", align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), - ui.label({ text = "Installed config", align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), - ui.button({ text = "Show Store", onClick = function() + ui.label({ text = tr("title"), align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.label({ text = tr("installed_title"), align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.button({ text = tr("show_store"), onClick = function() selectedView = 2 render() end }), ui.select({ options = optionFull, onChange = "" }), - ui.button({ text = "refresh", onClick = "getFetchList"}), + ui.button({ text = tr("refresh"), onClick = "getFetchList"}), ui.button({ glyph = "close", onClick = "onCloseClicked" }), }), ui.scroll({ gap = 12, align = "stretch", flexGrow = 1 }, rows), diff --git a/config-swap/translations/en.json b/config-swap/translations/en.json index 7a73a41b..3fda5aec 100644 --- a/config-swap/translations/en.json +++ b/config-swap/translations/en.json @@ -1,2 +1,20 @@ { + "panel": { + "apply": "Apply", + "apply_notification_title": "Apply", + "created_by": "Created by {author}", + "description": "Description: {description}", + "download_failed": "Download failed", + "download_ok": "Download complete", + "install": "Install", + "install_notification_title": "Install", + "installed_title": "Installed Configs", + "notification_title": "Config Swap", + "origin": "Origin: {origin}", + "refresh": "Refresh", + "show_list": "Show List", + "show_store": "Show Store", + "store_title": "Config Store", + "title": "Config Swap" + } } \ No newline at end of file From 7eb0cbcd3b7fd5bc1fb503e3aa955cd72ad243ed Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Jul 2026 21:05:36 +0200 Subject: [PATCH 16/43] fix: change order for set wallpaper after change config --- config-swap/config-swap-panel.luau | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index 72816085..6d3bd851 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -60,15 +60,20 @@ function tr(key, subst) return noctalia.tr("panel." .. key) end --- apply config file +-- apply config file and wallpaper function applyConfig(data: config) noctalia.notify(tr("apply_notification_title")) local cmd = "cp " .. configBox .. "/" .. data.name .. "/settings.toml " .. "~/.local/state/noctalia/settings.toml" - noctalia.setWallpaper(configBox .. "/" .. data.name .. "/wallpaper.png") - noctalia.runAsync(cmd) + noctalia.runAsync(cmd, function(result) + if result and result.exitCode == 0 then + noctalia.setWallpaper(configBox .. "/" .. data.name .. "/wallpaper.png") + else + noctalia.notify(tr("notification_title"), "cp failed") + end + end) end -- download config folder (support: noctalia settings, preview image and json info @@ -354,6 +359,7 @@ end -- Get all config on github function getFetchList(onDone) + allConfig = {} noctalia.http(requestFetchList, function(respond) noctalia.log("raw body: " .. tostring(respond.body)) if not respond.ok then From 3ec96341e9b9bf9980585f6a81917bfeddff6e81 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Jul 2026 22:08:23 +0200 Subject: [PATCH 17/43] feat!: add settingsPath for data.json --- config-swap/config-swap-panel.luau | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index 6d3bd851..db3fd8fa 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -3,7 +3,8 @@ -- Local variable -local configBox = "~/Config-Swap" -- path for storage +local configBox = "~/Config-Swap" +local pathLocal = "~/.local/state/noctalia/settings.toml" -- path for settings local listConfig = nil local optionFull = {"all", "not full", "full"} @@ -60,12 +61,35 @@ function tr(key, subst) return noctalia.tr("panel." .. key) end +-- function get position of settings noctalia + +function takePosSetting() + local dir = noctalia.pluginDataDir() + local path = dir .. "/data.json" + local raw = noctalia.readFile(path) + if raw then + local data = noctalia.json.decode(raw) + if data and data.settingsPath then + return data.settingsPath + end + end +end + +-- + + -- apply config file and wallpaper function applyConfig(data: config) noctalia.notify(tr("apply_notification_title")) - local cmd = "cp " .. configBox .. "/" .. data.name .. "/settings.toml " .. "~/.local/state/noctalia/settings.toml" + local pathSettings = takePosSetting() + if not pathSettings then + noctalia.notify(tr("notification_title"), "Path not found, please check your data.json") + return + end + + local cmd = "cp " .. configBox .. "/" .. data.name .. "/settings.toml " .. pathSettings noctalia.runAsync(cmd, function(result) if result and result.exitCode == 0 then From d1aca27a63b4a32ab791292751cff5c11e3094ba Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Jul 2026 22:08:50 +0200 Subject: [PATCH 18/43] feat!: add data.json creation if not exist --- config-swap/config-swap-service.luau | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/config-swap/config-swap-service.luau b/config-swap/config-swap-service.luau index ee8a9f76..142723f1 100644 --- a/config-swap/config-swap-service.luau +++ b/config-swap/config-swap-service.luau @@ -1 +1,17 @@ -noctalia.mkdirAll("~/Config-Swap") \ No newline at end of file +noctalia.mkdirAll("~/Config-Swap") + + +type data = { + settingsPath: string +} + +local dataInsert: data = { + settingsPath = "" +} + +local dir = noctalia.pluginDataDir() +local path = dir .. "/data.json" + +if not noctalia.fileExists(path) then + noctalia.writeFile(path, noctalia.json.encode(dataInsert)) +end \ No newline at end of file From 8bf5894c7c07eb68d603ba4a639098b6d7262175 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Jul 2026 22:09:03 +0200 Subject: [PATCH 19/43] docs: add more docs info --- config-swap/README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/config-swap/README.md b/config-swap/README.md index 7e03197e..5f7e941e 100644 --- a/config-swap/README.md +++ b/config-swap/README.md @@ -31,6 +31,12 @@ Keep a backup of your current configuration before using it. Open the panel from the bar widget, then switch between the two views with `Show Store` and `Show List`. + +### Before Start + +To use Config Swap, you need to locate your settings.toml file in the `$NOCTALIA_STATE_HOME` directory and add this path to data.json. +It is likely located here: `$NOCTALIA_STATE_HOME/plugins/data/{user}/config-swap/`. For more information, please see the Settings section. + ### Show Store The store view fetches available configurations from GitHub. @@ -79,7 +85,16 @@ Make sure Config Swap is enabled in your plugin list before you try to use it. ## Settings -There are no configurable settings yet. +To swap your configuration, you need to set the path to `settings.toml`. +You can find the `data.json` file in `$NOCTALIA_STATE_HOME/plugins/data/{user}/config-swap/` and use this example: + +```json +{ + "settingsPath": "path" +} +``` + +Replace `path` with your personal path. The path is loaded for the next swap. ## Requirements From 391e1e1be0c72a75f34bee3c9fe94c65ca725920 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Jul 2026 22:36:59 +0200 Subject: [PATCH 20/43] feat!: rm useless preview image --- config-swap/config-swap-panel.luau | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index db3fd8fa..85f6f1bf 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -13,7 +13,7 @@ local pickSlots = {} local columns = 2 local previewWidth = 470 local previewHeight = 350 -local previewImage = "~/Downloads/arme.png" +local previewImage = "" local selectedView = 1 From 3c8a97bd6e447d846fd2011d61f089da1636b28f Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 27 Jul 2026 22:40:24 +0200 Subject: [PATCH 21/43] docs: improve docs --- config-swap/config-swap-panel.luau | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index 85f6f1bf..16f7df01 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -41,7 +41,7 @@ type infoConfig = { depedencie: boolean, } --- section +-- section for get all config local allConfig: { config } = {} @@ -52,7 +52,7 @@ local requestFetchList = { follow_redirects = true } --- +-- translate section function tr(key, subst) if subst then From 5735e8bf831777f0c68c9152eaa1d8ec8d6c055c Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 28 Jul 2026 17:04:26 +0200 Subject: [PATCH 22/43] feat!: change false description --- config-swap/plugin.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-swap/plugin.toml b/config-swap/plugin.toml index 831154d0..7d5ede44 100644 --- a/config-swap/plugin.toml +++ b/config-swap/plugin.toml @@ -6,7 +6,7 @@ author = "TadomiKa-Ari" license = "MIT" dependencies = ["cp"] icon = "file" -description = "A Noctalia widget for applying Wallpaper Engine wallpapers from Steam Workshop with one click" +description = "A Noctalia widget for applying Configuration file with one click" tags = ["utility"] [[widget]] From 13ae8436680b46be8d6d6c5702c188e468a6b225 Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 28 Jul 2026 17:05:54 +0200 Subject: [PATCH 23/43] feat!: add shellQuote for protect bad command --- config-swap/config-swap-panel.luau | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index 16f7df01..6a57a873 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -75,8 +75,11 @@ function takePosSetting() end end --- +-- Section protection shell commande +local function shellQuote(s) + return "'" .. tostring(s):gsub("'", "'\\''") .. "'" +end -- apply config file and wallpaper @@ -89,9 +92,12 @@ function applyConfig(data: config) return end - local cmd = "cp " .. configBox .. "/" .. data.name .. "/settings.toml " .. pathSettings + local src = shellQuote(noctalia.expandPath(configBox .. "/" .. data.name .. "/settings.toml")) + local dest = shellQuote(noctalia.expandPath(pathSettings)) + local cmd = "cp " .. src .. " " .. dest noctalia.runAsync(cmd, function(result) + noctalia.notify("debug cp", "exit=" .. tostring(result and result.exitCode) .. " err=[" .. tostring(result and result.stderr) .. "]") if result and result.exitCode == 0 then noctalia.setWallpaper(configBox .. "/" .. data.name .. "/wallpaper.png") else From 91d6d03e45b406d4094030fe5cab286d758c3550 Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 28 Jul 2026 23:43:24 +0200 Subject: [PATCH 24/43] feat!: add path var in infoConfig.Change config type for infoConfig Type --- config-swap/config-swap-panel.luau | 40 ++++++++++++++++-------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index 6a57a873..dfce4345 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -25,25 +25,20 @@ local configHeight = 600 -- Type function -type config = { - name: string, - path: string, - full: boolean, -} - type infoConfig = { - name: string, - author: string, - origin: string, - preview: string, - description: string, - wallpaperPath: string, - depedencie: boolean, + name: string, -- name config + author: string, -- author config + origin: string, -- origin congig (Nixos, Arch, CachOS,..) + preview: string, -- preview config (path to preview.png) + path: string, -- config path (example: config/{name}) + description: string, -- little description + wallpaperPath: string, -- wallpaper path + depedencie: boolean, -- other depedencie for your config } -- section for get all config -local allConfig: { config } = {} +local allConfig: { infoConfig } = {} local requestFetchList = { url = "https://api.github.com/repos/Tadomika-Ari/Config-Swap-Box/contents/config", @@ -83,7 +78,7 @@ end -- apply config file and wallpaper -function applyConfig(data: config) +function applyConfig(data: infoConfig) noctalia.notify(tr("apply_notification_title")) local pathSettings = takePosSetting() @@ -108,7 +103,7 @@ end -- download config folder (support: noctalia settings, preview image and json info -function downloadConfig(data: config) +function downloadConfig(data: infoConfig) noctalia.notify(tr("install_notification_title")) noctalia.mkdirAll(configBox .. "/" .. data.name) @@ -288,6 +283,7 @@ function getListInstall() if not data then table.insert(listShow, { name = entryName, + path = "config/" .. entryName, author = "None", origin = "None", preview = nil, @@ -300,6 +296,7 @@ function getListInstall() table.insert(listShow, { name = data.name, + path = data.path, author = data.author, origin = data.origin, preview = data.preview, @@ -332,7 +329,7 @@ local function renderInstall() }), ui.row( {gap = 12, align = "start"}, { ui.button({ text = "install", onClick = function() - downloadConfig(data) + downloadConfig(data) -- here end }), ui.label({ @@ -404,10 +401,15 @@ function getFetchList(onDone) table.insert(allConfig, { name = item.name, path = item.path, - full = false + author = nil, + depedencie = nil, + description = nil, + origin = nil, + preview = nil, + wallpaperPath = nil, }) end - listConfig = data + listConfig = allConfig render() end) end From 38614bd812d614b3299de68f818cd2c4f50f7fd7 Mon Sep 17 00:00:00 2001 From: Lucas Date: Fri, 31 Jul 2026 23:10:27 +0200 Subject: [PATCH 25/43] feat!: add secure delete config and confirmation for delete config --- config-swap/config-swap-panel.luau | 62 ++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index dfce4345..495fd2e4 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -4,7 +4,7 @@ -- Local variable local configBox = "~/Config-Swap" -local pathLocal = "~/.local/state/noctalia/settings.toml" -- path for settings +local pathLocal = "~/.local/state/noctalia/settings.toml" -- path for settings local listConfig = nil local optionFull = {"all", "not full", "full"} @@ -177,6 +177,48 @@ local function queuePreviewDownload(name) end) end +-- Section delete install file + +local function delete(name) + + if not name or name == "" then + noctalia.notify("Delete", "invalid name") + return + end + + local target = noctalia.expandPath(configBox .. "/" .. name) + local cmd = "rm -rf " .. shellQuote(target) + noctalia.runAsync(cmd, function(result) + if result and result.exitCode == 0 then + noctalia.notify("Delete Ok") + else + noctalia.notify("Delete failed") + end + end) + render() +end + +local function renderDeleteConfirmation(name) + panel.render(ui.column({ flexGrow = 1, gap = 16 }, { + ui.label({ text = "Are you sure too delete " .. name .. "?", align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.button({ text = "delete", onClick = function() + delete(name) + end + }), + ui.button({ glyph = "close", onClick = "render" }), + })) +end + +-- Section for setting and reset / save config + +local function renderSetting(name) + panel.render(ui.column({ flexGrow = 1, gap = 16 }, { + ui.label({ text = "Setting section", align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.button({ text = "en cours", onClick = "getFetchList"}), + ui.button({ glyph = "en cours", onClick = "render" }), + })) +end + -- Section render for github Store local function renderList() @@ -252,7 +294,7 @@ end -- Section render for see config install info local function renderConfig(data: infoConfig) - panel.render(ui.column({ flexGrow = 1, gap = 16 }, { + local rows = { ui.image({ path = configBox .. "/" .. data.name .. "/preview.png", width = configWidth, @@ -264,10 +306,17 @@ local function renderConfig(data: infoConfig) ui.label({ text = tr("created_by", { author = data.author }), align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), ui.label({ text = tr("origin", { origin = data.origin }), align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), ui.label({ text = tr("description", { description = data.description or "" }), align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.button({ glyph = "trash", onClick = function() + renderDeleteConfirmation(data.name) + end + }), ui.button({ glyph = "close", onClick = "render" }), + } + panel.render(ui.column({ flexGrow = 1, gap = 16 }, { + ui.scroll({ gap = 12, align = "stretch", flexGrow = 1 }, rows), })) end - + -- Section for list allerady install file function getListInstall() @@ -308,6 +357,8 @@ function getListInstall() return listShow end +-- section for render all installed config + local function renderInstall() pickSlots = {} local tiles = {} @@ -364,7 +415,10 @@ local function renderInstall() render() end }), - ui.select({ options = optionFull, onChange = "" }), + ui.button({ text = "delete test", onClick = function() + renderDeleteConfirmation("") + end + }), ui.button({ text = tr("refresh"), onClick = "getFetchList"}), ui.button({ glyph = "close", onClick = "onCloseClicked" }), }), From c65c210448dd07163203d0157cba4911ab0caa4b Mon Sep 17 00:00:00 2001 From: Lucas Date: Sat, 1 Aug 2026 22:33:51 +0200 Subject: [PATCH 26/43] feat!: add save function for save current config --- config-swap/config-swap-panel.luau | 55 ++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index 495fd2e4..e8d3a55f 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -211,11 +211,60 @@ end -- Section for setting and reset / save config -local function renderSetting(name) +local function resolveStateDir() + return noctalia.getenv("NOCTALIA_STATE_HOME") + or ((noctalia.getenv("XDG_STATE_HOME") or ((noctalia.getenv("HOME") or "") .. "/.local/state")) .. "/noctalia") +end + +local noctaliaStateDir = resolveStateDir() +local settingsTomlPath = noctaliaStateDir .. "/settings.toml" + + +local function saveConfig() + local savePath = configBox .. "/save" + noctalia.mkdirAll(savePath) + + local settingFile: infoConfig = { + name = "save", + description = "save for reset", + path = configBox .. "/save", + origin = "not give", + wallpaperPath = nil, + depedencie = false, + preview = nil, + author = noctalia.getenv("USER"), + } + local jsonData = noctalia.json.encode(settingFile) + noctalia.writeFile(savePath .. "/info.json", jsonData) + + local content = noctalia.readFile(settingsTomlPath) + if content then + noctalia.writeFile(savePath .. "/settings.toml", content) + noctalia.notify("Save", "Sauvegarde réussie") + else + noctalia.notify("Save", "settings.toml introuvable") + end +end + +local function renderSetting() + local rows = { + ui.row({ flexGrow = 1, gap = 16 }, { + ui.label({ text = "save current configuration" }), + ui.button({ text = "save", onClick = function() + saveConfig() + end + }), + }), + ui.row({ flexGrow = 1, gap = 16 }, { + ui.label({ text = "reset at last save." }), + ui.button({ text = "reset", onClick = ""}), + }) + } panel.render(ui.column({ flexGrow = 1, gap = 16 }, { ui.label({ text = "Setting section", align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), ui.button({ text = "en cours", onClick = "getFetchList"}), ui.button({ glyph = "en cours", onClick = "render" }), + ui.scroll({ gap = 12, align = "stretch", flexGrow = 1 }, rows), })) end @@ -415,8 +464,8 @@ local function renderInstall() render() end }), - ui.button({ text = "delete test", onClick = function() - renderDeleteConfirmation("") + ui.button({ text = "setting", onClick = function() + renderSetting() end }), ui.button({ text = tr("refresh"), onClick = "getFetchList"}), From 95b05889752c6202ab07c74db5e6896c97bdd50c Mon Sep 17 00:00:00 2001 From: Lucas Date: Sat, 1 Aug 2026 23:10:41 +0200 Subject: [PATCH 27/43] feat!: add welcome warning and confirmation switch --- config-swap/config-swap-panel.luau | 47 ++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index e8d3a55f..dcc6a71b 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -15,7 +15,7 @@ local previewWidth = 470 local previewHeight = 350 local previewImage = "" -local selectedView = 1 +local selectedView = 0 local previewCache = {} local previewPending = {} @@ -76,6 +76,46 @@ local function shellQuote(s) return "'" .. tostring(s):gsub("'", "'\\''") .. "'" end + +-- Section for Every Warning + +local function welcomeWarning() + panel.render(ui.column({ flexGrow = 1, gap = 16 }, { + ui.row({ align = "center", justify = "space_between", gap = 8 }, { + ui.label({ text = tr("title"), textAlign = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + }), + ui.label({ text = "Welcome to Config Swap!", textAlign = "center", fontSize = 16, fontWeight = "bold", color = "on_surface" }), + ui.label({ text = "Please read this carefully before you continue.", textAlign = "center", fontSize = 14, color = "on_surface_variant" }), + ui.label({ text = "This plugin can replace your current Noctalia configuration. We recommend backing up your current settings first — use the Save button in the Installed section. Backups are stored in ~/Config-Swap.", textAlign = "center", fontSize = 14, color = "on_surface_variant" }), + ui.row({ gap = 8, justify = "center" }, { + ui.button({ text = "Not now", variant = "ghost", onClick = "onCloseClicked" }), + ui.button({ text = "I understand", onClick = function() + selectedView = 1 + render() + end + }), + }), + })) +end + +local function renderConfirmApply(data: infoConfig) + panel.render(ui.column({ flexGrow = 1, gap = 16 }, { + ui.row({ align = "center", justify = "space_between", gap = 8 }, { + ui.label({ text = tr("title"), textAlign = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + }), + ui.label({ text = "Apply this configuration?", textAlign = "center", fontSize = 16, fontWeight = "bold", color = "on_surface" }), + ui.label({ text = data.name, textAlign = "center", fontSize = 14, color = "on_surface_variant" }), + ui.label({ text = "This will replace your current settings.toml and wallpaper. A backup is not created automatically unless you saved one first.", textAlign = "center", fontSize = 13, color = "on_surface_variant" }), + ui.row({ gap = 8, justify = "center" }, { + ui.button({ text = "Cancel", variant = "ghost", onClick = "render" }), + ui.button({ text = "Apply", variant = "primary", onClick = function() + applyConfig(data) + end + }), + }), + })) +end + -- apply config file and wallpaper function applyConfig(data: infoConfig) @@ -440,7 +480,7 @@ local function renderInstall() textAlign = "center", }), ui.button({ text = "apply", onClick = function() - applyConfig(data) + renderConfirmApply(data) end }), } ), @@ -478,6 +518,9 @@ end -- Render Choice function render() + if selectedView == 0 then + welcomeWarning() + end if selectedView == 1 then renderInstall() end From c97b4b821775fe011f86ff143a8f675f007b7890 Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 2 Aug 2026 00:04:56 +0200 Subject: [PATCH 28/43] feat!: add info for store section --- config-swap/config-swap-panel.luau | 94 +++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 20 deletions(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index dcc6a71b..bb1b7bf5 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -259,15 +259,17 @@ end local noctaliaStateDir = resolveStateDir() local settingsTomlPath = noctaliaStateDir .. "/settings.toml" +local saveNameInput = "save" local function saveConfig() - local savePath = configBox .. "/save" + local name = saveNameInput ~= "" and saveNameInput or "save" + local savePath = configBox .. "/" .. name noctalia.mkdirAll(savePath) local settingFile: infoConfig = { - name = "save", + name = name, description = "save for reset", - path = configBox .. "/save", + path = configBox .. "/" .. name, origin = "not give", wallpaperPath = nil, depedencie = false, @@ -280,36 +282,85 @@ local function saveConfig() local content = noctalia.readFile(settingsTomlPath) if content then noctalia.writeFile(savePath .. "/settings.toml", content) - noctalia.notify("Save", "Sauvegarde réussie") + noctalia.notify("Save", "Save ok : " .. name) + render() else - noctalia.notify("Save", "settings.toml introuvable") + noctalia.notify("Save", "settings.toml not found") end end +function onSaveNameChange(value) + saveNameInput = value +end + local function renderSetting() - local rows = { - ui.row({ flexGrow = 1, gap = 16 }, { - ui.label({ text = "save current configuration" }), - ui.button({ text = "save", onClick = function() - saveConfig() - end + local rowsSettings = { + ui.column({ gap = 8 }, { + ui.label({ + text = "Save your current configuration under a custom name at ~/Config-Swap/{name}. A default info.json is created and can be edited later at ~/Config-Swap/{name}/info.json.", + fontSize = 12, + color = "on_surface_variant", + }), + ui.row({ flexGrow = 1, gap = 16, align = "center" }, { + ui.input({ + key = "save-name-input", + value = saveNameInput, + placeholder = "Save name", + onChange = "onSaveNameChange", + flexGrow = 1, + }), + ui.button({ text = "save", onClick = function() + saveConfig() + end + }), }), }), - ui.row({ flexGrow = 1, gap = 16 }, { - ui.label({ text = "reset at last save." }), - ui.button({ text = "reset", onClick = ""}), - }) } panel.render(ui.column({ flexGrow = 1, gap = 16 }, { - ui.label({ text = "Setting section", align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), - ui.button({ text = "en cours", onClick = "getFetchList"}), - ui.button({ glyph = "en cours", onClick = "render" }), - ui.scroll({ gap = 12, align = "stretch", flexGrow = 1 }, rows), + ui.label({ text = "Setting section", textAlign = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.button({ text = "X", onClick = "render"}), + ui.scroll({ gap = 12, align = "stretch", flexGrow = 1 }, rowsSettings), })) end -- Section render for github Store +local function renderInfoStore() + panel.render(ui.column({ flexGrow = 1, gap = 16 }, { + ui.row({ align = "center", justify = "space_between", gap = 8 }, { + ui.label({ text = "Info Store section", textAlign = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.button({ text = "github", onClick = function() + noctalia.copyToClipboard("https://github.com/Tadomika-Ari/Config-Swap-Box", "text/plain") + noctalia.notify("Config-Swap", "copy link success") + end + }), + ui.button({ glyph = "close", onClick = "render" }), + }), + ui.scroll({ gap = 16, align = "stretch", flexGrow = 1 }, { + ui.column({ gap = 4 }, { + ui.label({ text = "What is the Store?", fontSize = 13, fontWeight = "bold", color = "on_surface" }), + ui.label({ text = "The Store lists community-submitted configuration profiles hosted on the Config-Swap-Box GitHub repository. Each profile bundles a settings.toml, a preview image, and an optional wallpaper.", fontSize = 12, color = "on_surface_variant" }), + }), + ui.column({ gap = 4 }, { + ui.label({ text = "Installing a profile", fontSize = 13, fontWeight = "bold", color = "on_surface" }), + ui.label({ text = "Pressing install downloads the profile's files into ~/Config-Swap/{name}. This does not change your current configuration yet.", fontSize = 12, color = "on_surface_variant" }), + }), + ui.column({ gap = 4 }, { + ui.label({ text = "Applying a profile", fontSize = 13, fontWeight = "bold", color = "on_surface" }), + ui.label({ text = "Pressing apply replaces your active settings.toml and wallpaper with the selected profile. This is not reversible unless you saved a backup first from the Setting section.", fontSize = 12, color = "on_surface_variant" }), + }), + ui.column({ gap = 4 }, { + ui.label({ text = "Community content", fontSize = 13, fontWeight = "bold", color = "on_surface" }), + ui.label({ text = "Profiles are submitted by community members and are review by owner.", fontSize = 12, color = "on_surface_variant" }), + }), + ui.column({ gap = 4 }, { + ui.label({ text = "Rate limits", fontSize = 13, fontWeight = "bold", color = "on_surface" }), + ui.label({ text = "The store list is fetched from the GitHub API, which limits unauthenticated requests. Avoid refreshing repeatedly in a short period of time.", fontSize = 12, color = "on_surface_variant" }), + }), + }), + })) +end + local function renderList() pickSlots = {} @@ -372,7 +423,10 @@ local function renderList() render() end }), - ui.select({ options = optionFull, onChange = "" }), + ui.button({ text = "?", onClick = function() + renderInfoStore() + end + }), ui.button({ text = tr("refresh"), onClick = "getFetchList"}), ui.button({ glyph = "close", onClick = "onCloseClicked" }), }), From d6f1fe345ed47250238a801caa1361afb5a42d36 Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 2 Aug 2026 00:12:40 +0200 Subject: [PATCH 29/43] docs: add new info in setting section --- config-swap/config-swap-panel.luau | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index bb1b7bf5..682e93c0 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -297,7 +297,7 @@ local function renderSetting() local rowsSettings = { ui.column({ gap = 8 }, { ui.label({ - text = "Save your current configuration under a custom name at ~/Config-Swap/{name}. A default info.json is created and can be edited later at ~/Config-Swap/{name}/info.json.", + text = "Save your current configuration under a custom name at ~/Config-Swap/{name}. A default info.json is created and can be edited later at ~/Config-Swap/{name}/info.json. To set a preview, you can add preview.png to the config folder. This also works for wallpapers using wallpaper.png", fontSize = 12, color = "on_surface_variant", }), From cf4dc5c5256237ae415419b922c45d3a21facaef Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 2 Aug 2026 00:25:51 +0200 Subject: [PATCH 30/43] feat!: add resolve state dir for check all state dir possibility --- config-swap/config-swap-panel.luau | 36 +++++++++++++----------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index 682e93c0..889835a2 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -4,10 +4,8 @@ -- Local variable local configBox = "~/Config-Swap" -local pathLocal = "~/.local/state/noctalia/settings.toml" -- path for settings local listConfig = nil -local optionFull = {"all", "not full", "full"} local pickSlots = {} local columns = 2 @@ -36,6 +34,13 @@ type infoConfig = { depedencie: boolean, -- other depedencie for your config } +-- Section for state dir + +local function resolveStateDir() + return noctalia.getenv("NOCTALIA_STATE_HOME") + or ((noctalia.getenv("XDG_STATE_HOME") or ((noctalia.getenv("HOME") or "") .. "/.local/state")) .. "/noctalia") +end + -- section for get all config local allConfig: { infoConfig } = {} @@ -59,15 +64,9 @@ end -- function get position of settings noctalia function takePosSetting() - local dir = noctalia.pluginDataDir() - local path = dir .. "/data.json" - local raw = noctalia.readFile(path) - if raw then - local data = noctalia.json.decode(raw) - if data and data.settingsPath then - return data.settingsPath - end - end + local noctaliaStateDir = resolveStateDir() + local settingsTomlPath = noctaliaStateDir .. "/settings.toml" + return settingsTomlPath end -- Section protection shell commande @@ -110,6 +109,7 @@ local function renderConfirmApply(data: infoConfig) ui.button({ text = "Cancel", variant = "ghost", onClick = "render" }), ui.button({ text = "Apply", variant = "primary", onClick = function() applyConfig(data) + render() end }), }), @@ -123,7 +123,7 @@ function applyConfig(data: infoConfig) local pathSettings = takePosSetting() if not pathSettings then - noctalia.notify(tr("notification_title"), "Path not found, please check your data.json") + noctalia.notify(tr("notification_title"), "Path not found, please check your setting toml") return end @@ -132,11 +132,10 @@ function applyConfig(data: infoConfig) local cmd = "cp " .. src .. " " .. dest noctalia.runAsync(cmd, function(result) - noctalia.notify("debug cp", "exit=" .. tostring(result and result.exitCode) .. " err=[" .. tostring(result and result.stderr) .. "]") if result and result.exitCode == 0 then noctalia.setWallpaper(configBox .. "/" .. data.name .. "/wallpaper.png") else - noctalia.notify(tr("notification_title"), "cp failed") + noctalia.notify(tr("notification_title"), "swap failed") end end) end @@ -251,11 +250,6 @@ end -- Section for setting and reset / save config -local function resolveStateDir() - return noctalia.getenv("NOCTALIA_STATE_HOME") - or ((noctalia.getenv("XDG_STATE_HOME") or ((noctalia.getenv("HOME") or "") .. "/.local/state")) .. "/noctalia") -end - local noctaliaStateDir = resolveStateDir() local settingsTomlPath = noctaliaStateDir .. "/settings.toml" @@ -423,7 +417,7 @@ local function renderList() render() end }), - ui.button({ text = "?", onClick = function() + ui.button({ text = "info", onClick = function() renderInfoStore() end }), @@ -558,7 +552,7 @@ local function renderInstall() render() end }), - ui.button({ text = "setting", onClick = function() + ui.button({ text = "Setting", onClick = function() renderSetting() end }), From 85f23136487779ada729a7e74ae9e1406f14bd54 Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 2 Aug 2026 21:41:40 +0200 Subject: [PATCH 31/43] feat!: add english translation compatibility --- config-swap/config-swap-panel.luau | 52 +++++++++++++++--------------- config-swap/translations/en.json | 27 +++++++++++++++- 2 files changed, 52 insertions(+), 27 deletions(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index 889835a2..4607b51a 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -83,12 +83,12 @@ local function welcomeWarning() ui.row({ align = "center", justify = "space_between", gap = 8 }, { ui.label({ text = tr("title"), textAlign = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), }), - ui.label({ text = "Welcome to Config Swap!", textAlign = "center", fontSize = 16, fontWeight = "bold", color = "on_surface" }), - ui.label({ text = "Please read this carefully before you continue.", textAlign = "center", fontSize = 14, color = "on_surface_variant" }), - ui.label({ text = "This plugin can replace your current Noctalia configuration. We recommend backing up your current settings first — use the Save button in the Installed section. Backups are stored in ~/Config-Swap.", textAlign = "center", fontSize = 14, color = "on_surface_variant" }), + ui.label({ text = tr("welcome_message_title"), textAlign = "center", fontSize = 16, fontWeight = "bold", color = "on_surface" }), + ui.label({ text = tr("welcome_message1"), textAlign = "center", fontSize = 14, color = "on_surface_variant" }), + ui.label({ text = tr("welcome_message2"), textAlign = "center", fontSize = 14, color = "on_surface_variant" }), ui.row({ gap = 8, justify = "center" }, { - ui.button({ text = "Not now", variant = "ghost", onClick = "onCloseClicked" }), - ui.button({ text = "I understand", onClick = function() + ui.button({ text = tr("not_agree"), variant = "ghost", onClick = "onCloseClicked" }), + ui.button({ text = tr("agree"), onClick = function() selectedView = 1 render() end @@ -102,12 +102,12 @@ local function renderConfirmApply(data: infoConfig) ui.row({ align = "center", justify = "space_between", gap = 8 }, { ui.label({ text = tr("title"), textAlign = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), }), - ui.label({ text = "Apply this configuration?", textAlign = "center", fontSize = 16, fontWeight = "bold", color = "on_surface" }), + ui.label({ text = tr("confirmation"), textAlign = "center", fontSize = 16, fontWeight = "bold", color = "on_surface" }), ui.label({ text = data.name, textAlign = "center", fontSize = 14, color = "on_surface_variant" }), - ui.label({ text = "This will replace your current settings.toml and wallpaper. A backup is not created automatically unless you saved one first.", textAlign = "center", fontSize = 13, color = "on_surface_variant" }), + ui.label({ text = tr("description_confirmation"), textAlign = "center", fontSize = 13, color = "on_surface_variant" }), ui.row({ gap = 8, justify = "center" }, { - ui.button({ text = "Cancel", variant = "ghost", onClick = "render" }), - ui.button({ text = "Apply", variant = "primary", onClick = function() + ui.button({ text = tr("cancel"), variant = "ghost", onClick = "render" }), + ui.button({ text = tr("apply"), variant = "primary", onClick = function() applyConfig(data) render() end @@ -239,8 +239,8 @@ end local function renderDeleteConfirmation(name) panel.render(ui.column({ flexGrow = 1, gap = 16 }, { - ui.label({ text = "Are you sure too delete " .. name .. "?", align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), - ui.button({ text = "delete", onClick = function() + ui.label({ text = tr("confirmation_delete") .. name .. "?", align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.button({ text = tr("delete"), onClick = function() delete(name) end }), @@ -291,7 +291,7 @@ local function renderSetting() local rowsSettings = { ui.column({ gap = 8 }, { ui.label({ - text = "Save your current configuration under a custom name at ~/Config-Swap/{name}. A default info.json is created and can be edited later at ~/Config-Swap/{name}/info.json. To set a preview, you can add preview.png to the config folder. This also works for wallpapers using wallpaper.png", + text = tr("description_setting1"), fontSize = 12, color = "on_surface_variant", }), @@ -303,7 +303,7 @@ local function renderSetting() onChange = "onSaveNameChange", flexGrow = 1, }), - ui.button({ text = "save", onClick = function() + ui.button({ text = tr("save"), onClick = function() saveConfig() end }), @@ -322,34 +322,34 @@ end local function renderInfoStore() panel.render(ui.column({ flexGrow = 1, gap = 16 }, { ui.row({ align = "center", justify = "space_between", gap = 8 }, { - ui.label({ text = "Info Store section", textAlign = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), - ui.button({ text = "github", onClick = function() + ui.label({ text = tr("info_store_title"), textAlign = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), + ui.button({ text = tr("info_store_github_button"), onClick = function() noctalia.copyToClipboard("https://github.com/Tadomika-Ari/Config-Swap-Box", "text/plain") - noctalia.notify("Config-Swap", "copy link success") + noctalia.notify(tr("notification_title"), tr("info_store_copy_link_success")) end }), ui.button({ glyph = "close", onClick = "render" }), }), ui.scroll({ gap = 16, align = "stretch", flexGrow = 1 }, { ui.column({ gap = 4 }, { - ui.label({ text = "What is the Store?", fontSize = 13, fontWeight = "bold", color = "on_surface" }), - ui.label({ text = "The Store lists community-submitted configuration profiles hosted on the Config-Swap-Box GitHub repository. Each profile bundles a settings.toml, a preview image, and an optional wallpaper.", fontSize = 12, color = "on_surface_variant" }), + ui.label({ text = tr("info_store_what_is_title"), fontSize = 13, fontWeight = "bold", color = "on_surface" }), + ui.label({ text = tr("info_store_what_is_body"), fontSize = 12, color = "on_surface_variant" }), }), ui.column({ gap = 4 }, { - ui.label({ text = "Installing a profile", fontSize = 13, fontWeight = "bold", color = "on_surface" }), - ui.label({ text = "Pressing install downloads the profile's files into ~/Config-Swap/{name}. This does not change your current configuration yet.", fontSize = 12, color = "on_surface_variant" }), + ui.label({ text = tr("info_store_installing_title"), fontSize = 13, fontWeight = "bold", color = "on_surface" }), + ui.label({ text = tr("info_store_installing_body"), fontSize = 12, color = "on_surface_variant" }), }), ui.column({ gap = 4 }, { - ui.label({ text = "Applying a profile", fontSize = 13, fontWeight = "bold", color = "on_surface" }), - ui.label({ text = "Pressing apply replaces your active settings.toml and wallpaper with the selected profile. This is not reversible unless you saved a backup first from the Setting section.", fontSize = 12, color = "on_surface_variant" }), + ui.label({ text = tr("info_store_applying_title"), fontSize = 13, fontWeight = "bold", color = "on_surface" }), + ui.label({ text = tr("info_store_applying_body"), fontSize = 12, color = "on_surface_variant" }), }), ui.column({ gap = 4 }, { - ui.label({ text = "Community content", fontSize = 13, fontWeight = "bold", color = "on_surface" }), - ui.label({ text = "Profiles are submitted by community members and are review by owner.", fontSize = 12, color = "on_surface_variant" }), + ui.label({ text = tr("info_store_community_title"), fontSize = 13, fontWeight = "bold", color = "on_surface" }), + ui.label({ text = tr("info_store_community_body"), fontSize = 12, color = "on_surface_variant" }), }), ui.column({ gap = 4 }, { - ui.label({ text = "Rate limits", fontSize = 13, fontWeight = "bold", color = "on_surface" }), - ui.label({ text = "The store list is fetched from the GitHub API, which limits unauthenticated requests. Avoid refreshing repeatedly in a short period of time.", fontSize = 12, color = "on_surface_variant" }), + ui.label({ text = tr("info_store_rate_limits_title"), fontSize = 13, fontWeight = "bold", color = "on_surface" }), + ui.label({ text = tr("info_store_rate_limits_body"), fontSize = 12, color = "on_surface_variant" }), }), }), })) diff --git a/config-swap/translations/en.json b/config-swap/translations/en.json index 3fda5aec..a917372f 100644 --- a/config-swap/translations/en.json +++ b/config-swap/translations/en.json @@ -15,6 +15,31 @@ "show_list": "Show List", "show_store": "Show Store", "store_title": "Config Store", - "title": "Config Swap" + "title": "Config Swap", + "welcome_message_title": "Welcome to Config Swap!", + "welcome_message1": "Please read this carefully before you continue.", + "welcome_message2": "This plugin can replace your current Noctalia configuration. We recommend backing up your current settings first — use the Save button in the Installed section. Backups are stored in ~/Config-Swap.", + "not_agree": "Not now", + "agree": "I understand", + "confirmation": "Apply this configuration?", + "description_confirmation": "This will replace your current settings.toml and wallpaper. A backup is not created automatically unless you saved one first.", + "cancel": "Cancel", + "confirmation_delete": "Are you sure too delete ", + "delete": "delete", + "description_setting1": "Save your current configuration under a custom name at ~/Config-Swap/{name}. A default info.json is created and can be edited later at ~/Config-Swap/{name}/info.json. To set a preview, you can add preview.png to the config folder. This also works for wallpapers using wallpaper.png", + "save": "save", + "info_store_title": "Info Store section", + "info_store_github_button": "github", + "info_store_copy_link_success": "copy link success", + "info_store_what_is_title": "What is the Store?", + "info_store_what_is_body": "The Store lists community-submitted configuration profiles hosted on the Config-Swap-Box GitHub repository. Each profile bundles a settings.toml, a preview image, and an optional wallpaper.", + "info_store_installing_title": "Installing a profile", + "info_store_installing_body": "Pressing install downloads the profile's files into ~/Config-Swap/{name}. This does not change your current configuration yet.", + "info_store_applying_title": "Applying a profile", + "info_store_applying_body": "Pressing apply replaces your active settings.toml and wallpaper with the selected profile. This is not reversible unless you saved a backup first from the Setting section.", + "info_store_community_title": "Community content", + "info_store_community_body": "Profiles are submitted by community members and are review by owner.", + "info_store_rate_limits_title": "Rate limits", + "info_store_rate_limits_body": "The store list is fetched from the GitHub API, which limits unauthenticated requests. Avoid refreshing repeatedly in a short period of time." } } \ No newline at end of file From 2441ef7bae336cc1acd9618af1d5f21f34e3d38f Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 2 Aug 2026 22:20:57 +0200 Subject: [PATCH 32/43] feat!: change thumbnail --- config-swap/thumbnail.webp | Bin 44736 -> 57732 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/config-swap/thumbnail.webp b/config-swap/thumbnail.webp index 14ef8b4c72ea1c0a16f2438db535b1c27b9c195d..fb975e84b8b551c1ac2a63681c8c211f6d08ce5e 100644 GIT binary patch literal 57732 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 literal 44736 zcmV(?K-a%gNk&Gvt^fd6MM6+kP&gp0t^fd#n**HzD!>CA0zL@>fk1&6000n{mZ!;W z*Y_*bZI>HCOJS(2Bb>9Ynazpt%~P|tfftznh}V`-2d^(O43m8%K9snM_Wti>e53Mz z@qT6dlX4G*e7-G1485B|#kwEF-10RPwO0sp(EU)q1@e}4Ye{nzqm z>yOTUr2agARsU7~59!~~J&iw7{fGYd{-gWH;Aj20|M_o&KX(6${b>B3^bgg4>i_cp z#(pRNzy4q2m!|)1|F8UB{Gj^<{44lp`LF5U^8c#;UH_Z@Kk}ADo@(y5s4u|(SN_fY zd-ccr$Nlf~|BioS{e$uA;2-usXulagfIpLeWd5)H!}a6)_x^9aJOID7{^$L7xCil% z=0DxPw||HK7yb+X$K3b*U+#Y@KfC|<_@n!0`|tNZ?7z|fs{b$l!~PfOFY!<1pW45( z|Be4O{k#9a`;Whm{@&cbc|YlUlK!Us>Hh!!{|F{Pt$7c>#2q_FyAYoCuM$h2NGJ`9 zWN&bNT7Fk2ik#oZFOp?|)C5BYpZZWSv;;6?x<=U^1y*>;*VBK z>#(9Z8LCw%i#V!%n{-vf}0A*Dlt+nRbu$7HulThd* z!ZkHEGAOKr-rFs2i1d79C>x9$wQ{U&rf= zGqi-f8r4 zuJZ=#Ank(BgLowreGJ_2nYjZXe$*ZXK@3#c-GmPLh4+KIss>@XcWTIJSbHLrB(f4X zX=&z)xpNOGQ_7DLu&rIN z^7wt0C`S{@odD4Vj+~GKc~(e?PHAxtk#)JKN`{9&m#%pvwwXkbu3~`-gWBIZDsSuG z1bK5jkQ-~?VFoB=dR14UJMfX89|gMc-(5D!j=DHu4aF5$cj9@O`0M7Um#1sc%?hws z(0mEM#^n%Cu3C~G<-)Um(L{7_hc8KE{M-zYMmvc zk+G*S!uT7o{oAl)5}+VvoI1j@fKb1}-QEGAOm!Jqt9a{9<@LOhZCrP@EbP@Yp46%} zHWQY{MSC`%&sPN6&lYHS50Ro?1fv7)O(&+^GYUDE%dqI#_u96EnJG;P##owYi0|-g zKBt^82iwivUI8R~rX%4n(=>Y6y8Mn!K4H9gQi5_eV-~i<0}lH8}||zhly}y<~1HhsQ^H=3|zE zK{a92h!)2n1YR&5Y}Tux)ZZyRq--W6L@sGiL!B75R@E`5zlj@8Z+ieK45mM1F*<(G zW(gBt#|t-IdX4+keXKJVz<=M_6pKE z!n6fN?X5xfz%k&qfyra}PdAopq^?RaF^V@8^x`adl%~#nf19;L=>8hLid~CK@(50s z)TQ0W@a%Wjs+2kcickpqJCE|c`%8yCzgc8+q?b+U&2VK8=;()t%EIk>Q{qkwWw0ks z;YKXpy5fuLYab=zj`42?=wg7oq?#@yR=%ea^8hEAdjiNTzksSYw-gngurC`K;jbc( z)uq&2i_RS^KraqioxLF~(qt(_gEo5CGeMg38u|fZpfMomrD-R8mHTLyY!L?XXqyqy zJ^N-~K|_S=a9wqBXNh~)pX+LY*=Z{FKv75llSQi3zUCZJ_7~kVgwBd<+rm;6E~>bE zYRr6&4{K<0!GO}dObu&b$)rZ4qB`1s7cY@GMD!{TA-J;zYE1UqhAadQ{NhV9t30ss zEBL=2M_vSS0qVN7bHc~ik3JG3<%^Nj_h;-Q5_{yHb;sn>wct@!qM6Ku;Av@76WyOT z|3{Ni*yAz1IK=vI+K23o`V!#MlCR!W?{LNZ1>;4;$0nA@Q!+t$7YT-@I_>VZ?#wCq zz^;)c?UOqs$vLG|k3t8}I08+b^-(n6!FdJ5o;9-|OiG>iz?aNbVnrf&T?s!(gq}NV zFLVxmgM$0yqwa3n8u;frtK--07{ZVmjAbuD8bwB1ogZxNs7q1cJpf<;$?t0MDG8F7 zwOQnvTJ0NFR(KuU8B@H5HWk=W+PbiCGV=F(0J1JftK!`_wF99@^kQ}(D%yFl3T zFitT+q#cB7Us`nH2kAI=@&+p$^1Cl+OMS4_iUn;N*reAO+4J4dJTitganU)e*uNBgd-YTC26Q_aprFo>u48Wr*`B;E%$Jk#N8PlisbY zRUII0%ErBTKDN%`$XgMcv@Tu@jPXzIxrIe3mCA2M|3W_sD#beV8CLDtr)$8V!&T#3 ztL<@tHcrk!jH+ULugdObzSo?Kht6v?xr7Jue=B=9%;aWFO>DUz3(X5?t`g^n1inSl+ zzg-zso`gvx#k>y4pMksygE<-30DYaPc#wnDhWsjq)&^>b@gnJrnit%{1Z z<^IM#Hd_4#jaIp?mq<8CLbRR|8zU6dNsmgt&nfj@wdF7$kb}uU-$X zpf~y)_2%vn=JT`|73UZhfwk$TTyWl)vP#|`!+fh0=e?*DlHp6f2Lm!GGge_{x zavCkGO4+d>E=vz2dWW@?qb4z~3l&F5gqI4lZAA6Ecid=6o5QwFG;4wCFwkS`lvR@p z!YuvI-s@(I41Bd!$jt>YZnCUaw1g_+_w;d>F&P7+?FlDdZNChMG{4Fe{Fd4`UTQLW zdD=Ft=rw-Bj0S0VeLcsjZYb{G{n4S z0gz2SfPDAmfFn$bDG4$~p_*91-qk2YJwQm{Z>r?EgKzU16Nc>QaD1#L1Uca9r7>0tTWlfJimif+MMIN;bYdIoHY~F4eLyg{3vBDfdD8LJ=%Hi?M-C zZ5vipenEG)o}54+RDiIG$ZfAU-0h-NeY8#Ldn*Q8UBlL8s;wpiB3xL9Q0rcj-^Dj| zjb!$0FWbPMNr1?fw3eJ9Y5MzmxmOIH&RLk;FQNn;K{tnpBrf6@&qGFZ=sU;f6iVQai#h1WJJ9*MDW18^JzAHPCdiT17OfUL2D z^5V)-ud>lNH8gP5Ud3HZpjzIxs@f80UDTEz`jytTMyK)uj)tol#XR`Q*|KujGOI%3 z1Xeta=}iSNytT>%{-nYu&(C@s{i7IC1L^)=hY8z2v{k7z8-u%Z zJ<3v8Jx^s}-_f}6A5Rbe9bS$ckJu?>qcV8!$J~FHh+Yy~N0@C!W)YfU!^^*1-(}LJ z(8q)bseA-B5;=0T12P0Z5fDL*Rzc;JIzTh{8)(LfFskH z`!FLZnu`f-RQN7)E;CzIVWilaFWMUNhT-K1)XoEDAUy-Ec(N6ZIu$?01X`4ncO&(s zY}-{(OAjK}vF=g06+JC>Q?zbz<~GDR2rC+Y0su!7GBsR_BJ{VL%RyDobwOtR<;?#Kc3v90d(HNW+? zY~g6DblR4^@1gX<@IKG0YNuwl^zFBI4rR67bv~XZ-88x}{aeE6)jxoTj_U0upo`|A zG}*vkKfGYgcW&Gb5k4V>CZC=aj>4H9Vn?ai48_Uq51J8dv#oOdu^V*@swvKW%H}iF zu|wTq;O^C2rTPEyx%Ymc_+9N#Dl_fl3hDqhMIQi+E?f*71o9C^EHF*urHLU)6O2V; z$go>vTXrxoI0ip{F_q!>ZB$$V3Bzd{@yCmsdIZvW=@hx@g=l-;XvqgxTxDN&Ti?~%m(h^>_*HBf%PLnlJ$vA$Y*GuxS@95m5XIG7K zk+-ISplHc6IUd2~ifR<8)M^B^Rkth3B`pF-q>>7P1(US-%pCS^M0XgInJ&-rD1EWq zbjVuzwFL)+214owu2hywQuF{Ju)t+GVsXx|El6gL%xmVFfzAeNaa^Ll&ZSlnl;jjL z7Z12!z=h5>*Xo`_O+}QR6`0md+5Z7arWsvs=D`gP%ONzNE|Tyi+Lbu`{wzs>!IW)` ztQl=zg}7q^fdjQ>+J;hTkxhnf8V6YGzn!e(BTT({&I!4_8R&OlZwgU z7?=3)i?1=39*QA z5-wv7UcJf(#n#_`rRpR(vb5^(Qae(k@ zt#e|i!jTvTUIzIWXIH8;I)IZ`h59)vT8{B`FY*p~-+RKoTpd=Fk{{gH}=8 ztF1*~9y}AbB(m& z(3cOSQkhaZ*}O-&!LLD;Z(9J#a(cxve~rTpCoiHc|FUrHeql~$as48X} zm13WZ@@gnwK8?3xqk_~Y4A@==>ablEbJQ#aL!l`9BP}jQ(X5?vS4OyD!9J*4a|((! zF|K!RKB(6u43)Q8qbD2bQxM zq!1$)W8_{CtatT8J>w^4(BL~Kev_-3K`HZKgQj@MO=HoCqq{8BvQ4D<9v?hIy%W2mEpy`5f z!+_wrH#dkGUd1{(T6bL$hPZMT%JHMkj$3vM4zdta## zKsh?xKr!98`lDP(s9&JK*E#W%DvHbkYi1kZ*v+`rTaaA%^5`KR9*4?qN3Vv?0*nix zA9`K`k-ExM9VV?0zjcDOHv1&fH(Zja?JpygMzaeF^9IwAR9wT+{Q*AXRi{R|IEs%X zF^Tj|zk{43i(#i!HDJ&WsACP8K;N*RnQc|>$zCmLjdE}oC5Ms1uGQ6pcnuqm<9PhE zm5QUJLNgguK$p~TB zD;KMkaLM7f?Qw}}^Kixk!G5D!(%9W8g)kMHI#UBc7TiL@9FA~weN33Je#6Hd@B)1Z z)P1<%9J}JoJQ7NVwEz>yHN~jTky>nfooFUHJ$0}EVp$lAFkAhcMnqH_txT7&#tI9m zyBdSEE(Vei`>kOd|Aa=Uv)lY!1btD46Y@F5T(EgLp{6R2KzB}J_7>{i5t4YSIy6eu zpTt6_31)gu9Bv*z>r?G`59BH-ZB>5$6QVT8P&6IgO~TSmXh36`7Q&*G!Y?qWq`9vTdhs}Z4_;d>a)AP|*MS7B zGuJ|)Ez{^${PGSV1RQr;xKcDvc4L|(+0$@&u!BPNTS0 zzL&qfA6S?b91reI9uNTX0%fD^54n@ow77|_8Og`}wF)kd!&~*J?bnBKy*KgQUz;Br z+w9xvzBeZzg3WG+`P4AV00u_B3KQyvkdaKGep3TEdntM+@CG`T5^FP?gSIQQ9U$P2 zK=eA8ic<-k43@#wig?O0a;&kAs?2FGxJ2<`ZOkeuNNnX}0&o<|_+5uxBXfS!GDAzt zefwc@+7N&XaV;LB|2?c7T#jeCN=qG%GYj19gq5{o;*{@Oa_bHym}T_Kyly#?OdhKZ z?KqX?kN$%o`HEdXb2Aq-D*e-@SaCtE2vD7E$=zdF$c|dV`O*b2eW5}Z+=rL$Ax0hx zbl)S1j0f%;ZJ+CoB83x?`2A?z9@W$xNmsjJ*5wdymmt7xIq1r;Se+@w(NMkA70r1i zoxaxHP6l+AR}X7gcK7WXeoi<_X9R!PPudr@1}a*1RLn0rw@+81>r0&6eizc{l1-dA zn^-mI4fcBU8782DM+r&fYYr^+2A)sS7U;o+VdvNs1nTIh|D?Obc1WPwU{v;Za#UK< zI~bdhP4CEiMoH<&eABf|3VKgw2g!Qp&!)3A8Oa+Fccv+p=sV=T6IM#z$&z%%;c+8n zGzGBvnHPHtpvRsxa#$uevUa+bP(AJGd|4Stv8h!9Vj<`@EaC$euyX#ai;HZBAQn&Aij$E=;zAXSdU7KHQ~uHw*|4L@NU&Lx~qq zbbefpbg zQCD%_WMs899vWMj$GVUUNC&qirmc?yG`I`ZxEXizOmdwXIFVz}vM4z=EwjE8?VO(f z&o}X%CgUOyou=d6Tw#tnj{Sf&yLwG_BEm$7!qg#nQDiqq>J=mg-ZF+otUnBD4{2q+ou|oPZT))v;N6Y#H_uG ztB`DyZ%p?HO%p6VgnMsM1`v*?6gL**qvc078dV_D5?l01$ZeI)VaFfFm7V!pE^Rv7 z&w)lA<38SwK)K`(y#JJ4;H$^JyQMN@Q96hq#H&Z%x4x0!XaQ%aKV13^rIy0q+!njk zSEMjT=q-x7r>a$+w>|mV70^iMmTGQ({;MN3k!9|p>}sPI?Jm=lG7rS$eYhh(Wbabx z`JY>6JLRzgSm%d_*k#t_h}FZL+CGJ4_^Di0~K}eQ>gIj-icqf#356F0RI2Icx4^bKm7D- z{dxJOACJbPiSoSGd3CeJJ@)a*$%!XvCIbw-0t=r@aDEKFxm!x6{h00Klu3~xtIjnD z9oq1+^=F{4;ynF)M(7wM6)N=kPgfRnG+od;Py-iqvCBZp9eLsfyOLIu&P$~x6?B;> z9Ll(YWs?PR#x-z7@1{+GI(PCB)n2;L?i7N?e!QwCz)M?*^IYeSESquv{R&0Zg+-^1 zJZSJZp!gB01H!wS=<*I$Z{xVJi}XRb<>)qsEE3L+TZ19m<)-(6cZP0CTA zP-zqBTJ~2jbKF#*F%rN|4Y~%o!zP~Hn1B$j4nDuCsnBpZ9H9BKGV4s9UfVA`t;4MxIfdqt+8K{6e9>YyALAovLwCNM@QJ+i2WP~d7n zO$cHP9gsKyPn<#yvSY`79c??(hKLxQpb#G@BEa|X3q7nH5NNplEa0W3_J<%w0Un5B8+G+NgW^R}Y2 zs@fh5;$_9tY1MxH(@M|HMg=<=sE9aNQlO~o?x9P6J_w;;O(tVMDWz>#Zf8u&{ynpP$GSyTtJjoSKqvk%ch5p6J!@RWiqKIB+ z3z_&p%RMY~*6O@GLp909fe?Z~YQ1wlZ%}hU1fi5fhPP9)h%R1&4BYc7FUYTsC{cVm z2)Xii$K3c=?ZK%Nw?>L~NS?5S(`5#v03Z*UVX3PDn*?#zpOHK3rNC#%AcD$DLVgm= zq(hkxDB$>cD^T|bAT;T{$nd@1 zn(xQL!YbzaxzmB-89Qnyd{K@M#EJI!?d~}kLY`>_=XQ2+s-3RC7k~(JO2Tf6H!tj$JSbjk0N2r{;G$zUQ(GadJ{mB@Hkx~fF2>ZJQ371x_QYR0ZUr{0o<*s_%-SdV9OEAs(Ug6`?8qGX# z9N8XHDgk_Uo`4y0U98>|BVp2^mvSGlmRr*Oe^^aT>pnUDqp=-LuJ;cAk5~E2x@rCJ z+SI(wn$exCGGSz={!e(0CO_jBYA4v2iCd|_r&6uz%5yGsB)+==0~1fmm?hp&%6hQ$ zf)D9%jGWYU$4BV9S3Q~+hHL=i+vBU+2%{u40EnPunjEnl4H@78IC-Wahy54TGzs`* zM&wOyh2iXbLKk=CHcZ%I`i*;kT)^uxqS;zxFKfH>AvW80;~Q`v?~m7*F=pUgTe|f! zI2`tZ+>JekTN4U_K5!R0|F&_il*A9e07ywkLD{qC)YwJx0$}|wDG&7<>yI!4SRIdC>ha!=ykCtM}s4z=fW6E37lL2lw2y zqBj6_TSAxEV{O`c*zB{PP zGc3TJpH5IMeVlSV;y24d=IT2Cz{o+&LA8}0J^#^it&=hjsoi&C30@(8u0gC!{-i?kN?KTUsSb;&f!d| zOdihw)^Pcb#eNHK?GEW+LwO=~*eT%GQ1sh}eE)SrjKBq$)oumbu?uaZ8wb}y9jyo^ zmE&jOgM55o2shao>l~x$;W?Wf zaIb`K%)wi6Y6w^NGROA!`X2Do*@WHT7sU=N8a5F)8IuICTX!;3bazeXjH8FKvu%5-dCkX3;l3)eh-y;#iBjF}|GG zN3upcu=EqHM(uRzWn;P@>huN-u<3%_f`JsSjLpG_EmZ!gj!|4nG=>E!MC1@}^GGM; zld2tz;=yCEhoQcde5ItmU00e9Zntf}6~PO9Dg{6Dk5+P-H4lS`^Wk5eGQIXDt#gB0 z-3D4<+VLc*9dC|eHr4kh<*cOPFTFHs0;zj0+rx`ya6j!;+4F(52yG*O^V4`9Qo9a` ziUxl6;8|LFDSS^;(~k}~nr(#(gI66<0ELRjZZu>8L#t;(8f>7d=-#lRDI8iG4hVnM zZOFQa`0$rT4KGyw-WWy8tek-AyQ-@|APD!*eX&hJIuoVs4{|A+)yg1V&mgDk0%($T zCss4}Zsi!$F7zuyD6A!CQ%l<8ZT8_rnj(JO*Z0e{^Ahd^WX;r?XX9tc^3K7-3Oivb zCc>P@N|i}_WCjlY)bAE3<({+^e#a??R+tISViL;CXS1+f%BHmO=1t9Wy-xEhMswsv zuY&3rq&hL_+&6y6PMLO5e)MObJJbZeT^)JjdgL>KP$K2pk_cB`M-*gyDe)`638 zGog;qlvSbwWg*RG>UwPQAsemAgRL^_*^Hmds~dfStPM#{StkTqYUiaVr1`2z%!z%d z!xmb3J4hK;A4QGc`qKi0^wn5!Z0|ct*rk%Jh3KRJkyh`nG`;!o_tM?F8mcB5$cyn8 zn|cjRr>}~%Av}xSUWl{(FHajiZ*2L~NbtLaWC0PNdDrA?|9dITNt_PBOH1^hx2;ou zs@qBEl;I}*j1&NOyPS_R*B>f5>$@zM-W+Vp`qW3O3}+>6=dS583RI!k_UxZXKBD7& z-}_P;l#{mn$Hm2AC zpV|1>7bJ$a-KrzV_~Je|v;EjLGPaVOhw{J)^o;_%loQrk=qCi*=eMyz?^9@Bq{0vb zJx89noT?X05Bp^jmA_IzDp0@97>d8=3Q!_3d2x5UgOqGFRvi{;(pIc$^Emi;WA@ta z7-Km^|FXPN^gTO=#5U;`120scjvGn)Vt|-1b~ygga)3S-A1b6$P|CcO|0tG9hd$YT zNQ@)CDNHq{St@cp;rx!Z@hbKBC<49KKTS6Gj(_|1*~5a;Zqo2ORivA0RS1ok!4w&I zM%0Tm!sS)|{LJF3caEFXGWnH2MC7eSG3MqhP1@rxk%Sc2##Z*>dWUQ$*X)U!I}dZ6 z4lr$^7Mul#zBsAP+kup9s{~eBHU;o4wQ#bjbkmPB1O{)>ZYGz1+YjFbWEo9TW?>6J zqizZ?P=`YX0^7-7{1s@XSooaBUHbF_z>JAK8fy)jW+&%j%0`!>1(-c@VR8cdV;LpY z4>ELG*`9^q_{htQv_O^^oMnBy7=NE|K=)vG!M^pJ_rSC1Yi>LWk>d2Cu@+;9;}$3q zqU1nW=|ibdIW%(y$YWk$p1=(Ee?N~FDcdq%HpT|SUf2%NtiDNtoa$xjg#`|G0GIEy zHI!5M^oCu+#xfO zZ*o5^iBum#qm?-~#G|Vy1riV5mY|-OADrrEt}ENRI5W9u9e&n5y7*=RUHnTdSzKVH z7SAsFhY4W;B)>ZqX#9?nUb=VEOAPu*|Z3Ur}Uq~&b6Z@zuFoSX*ifZeYc2w zQz9;im>3Gp-~Gh*CA$z)lDT6|tW(RBrv>Rdpx4B#mSe&0=}|$$b63|VA%7XEpoqKQ z?0aejnRr>}nYGS4T!kLW0Cj2!216Lr?7k*ve4c!LI&$nPx&{-pN0{Fkore9p{M=fo zALRA#D-jlG%03)E(abErdn4b*7AK5I=ii#f0LBYq@={fNDn38*oS~HbAB)BdF zW^7T0Isb@6?7zZmt0y(VJ+z5~X;_eP8y8jf+Oark;}6uu$mI~<0P1x@248-g(t4yU zK@waeUF?RDQdWlW7Jr+QnoCe_4LEd}i?iB3m(cBSq3ilu6 z{eg)ojBVW%Pi5GIO8v8Un1fqFJX`NaWjd?^n1K2|oQL#a28COsgKf)g6^By;n3a@~ zRdLg@o@NZMVV7~eBPbh@9QTtwia)jq4QT$o5yRj@dlh6oQKo z3yRtn=dG6{h^nY8x+tW}2>t%5OCCkj`D&3PxHz7KJ05j>v(U+YF0_5-uFty?R6_ovKRVn z%(PGE*UKDiuDGP^L;LX6oM-eVqfM(dw_16PM+Ra!*%O)CJe&yiaUk~``T~2l?>K); zU{Z|I2RxfzVP*P_TTUdkpA@O(dkP9;T^qEjl%3?t*!EZ>3+CTwk@QMqaC+YhTkp)6 zuEk(=N}$63tOK%D_|L=oWVoyKpi|kpe^~L)Cc$N5c%c|Yjr6Ve9l2BWHU+pT{cTKT z0O-H#Nua`Odda!xNi5wYtQ1@(AQY<#6?5|aR`t0|b)-UIsuNQ#IxTf#iW9++q2zJ_El^=fnP25?i7 zvQr)d^YY2V@P^MSHWG*#rS&k{^65w8N5BmH-5xCi|DCTQiSX5Q3k0 zWquMbjf@ie${y+$f7C1Y-aeyZ9820w$xG##`Q=ws7U_-QYJ#>eI-aZowJq|8O!yOQM40W)GY{nji4S8=$KHMU>Yvf&psk%78|G@;k*Lz zqDB%D^#!CvuJ>tpGkBDK=MhAtW-XHE~wco3hdbwZ4?%O`#^doPpa%}9o`fJ|%BKLi)iq?!?cgnZD68)mdck;XV zK^<(tGLgvms07+HA=(|s!U*6Z6Rf{;>MVIr{-3A%lA@J-@ z29$|kF9~Vh9hURFW0203lb5|#lauft1C~KzZ_e>zw1N>T5l+3`Q7Tzgou|ZiiqmjZ4njn z-QD^-O{?E0%e#N-U+-4Ihf`>LHXTf-NionIbaYA>$N=l8Ltq~{Nl{$d#_~eNfyJK zoG=%bAknGsIaV?&m$=GS7mrvHcHC3&7XE+CdsU@1_0>F3op|mj@N4q+i5tfa+X%l! zPbe%|YL8@su>eOl$#__{=c$?^+%nF?3)-)dXEeY_w!%QKIg>D1CypW9h)qoySRD-43{ z0^hxOAaY|TtK?mz=5#95cmQu-_zg0TxBEIhb{CWd`{p`Nz%Sb;wIF`9mh0J%4qH+3 z2CM!2>SXuXJ-iOw=5`rvvH*=Ese|(8;Gg{1M6@Rh2%z3}AQwT>3b7sUuXCxix#wCU zMu(|uAXxq#S@6&8gn-WJKqPve0KO&{iirgQ|9kHypf0o+rwhG%ZIpN9j`$!_(=!j0 zz^@! z#G9VtPpoBoul?FWHhr)Bmex2Z zr`rO!M1#N|M=O~1Ld!peWeCeZAU_H8sKdBlvh~Q?)Ui55w-YI1d|${}(c?YRL?ha- zCX-9RdobC(FzZQ0+rC}@+i3iQz4vGo+^2=D`W0NJB=3B84g^6wkZZjR$_c#BnNz|Y zo#`rr7M+XZfk|D9_rUk<}Qyh6GCOxG%#_UL2uO%3i=6U|BKUnbA9 z1tli~QQfM=MR>6^asD(K7d2FjBnMPLZUoxM8CP|aeJ2cMgCciuK@FGly%(LNPC|Mj zfVG4z;R9;9;$zfMF&)!$>H|kLIv(|Qs5&VYP1dn!{gp#Yo&-44-PHZRbtS*)xHsiY`qGI+p+2*}c|uK?tif`9 zWnYC0uSx&w<90+0{VFV{NcSu-CNZp~ZV%KSeHFoaVgB``daC!H&FzO1npqy<4gQRF zwPl4UJ|N3~5@{?NAJ7f@nL^SK_b2e^gS#v)E}(B@5k;N@Kmj9I?WqUL3^h|?tdtqA zA&?6Zs^v51qz4IF?cw082_eePXuyMf7CCda;)Cs(Ya7yvG-SgPjziRcN%03_N#ix$ zvO#OuaEP@KVlcmN=M(<3*);I=7HL+BCbr!%HuIb5&v+)==u`c3kP zUaD{^!zXfc;Q;3EtLqDU)A7N{2M%@xHB@ag987Xlr_etkZu?`GYPhhy%u0fY={(}G znbI4`I>-|Ti6@dqWjlZO9j-;gMYg}E^Ut{cOMkZFAaoGUR*~)ZK8qclk9wxiYcZrT zM*l`ckrth&`~ah{GJD{{HKSe(l3|f@Z=j#UGSo|EC8*?A8&5Em(g0Lhhhkq88ymP? zG1kjtAb3D$XbZ+ zHRRraSDj%uxil}(u)|l`dLB`!D};FEX77R3WkD4&vZU>OPce$R+i*jOfnD!Q!xK{K zp6cq>!)-wS`SISr09Za#TA0ME@p@!52KM zk~r%_5h7eK^iw&{GF5?TWc;CJ#ZCE+Q{H*r%=+eKRBK^|Fh=mUjOvKdRItxBNeTq@PApZ73pmz5CzhDt*o~~Rt zfBCkA2$REULFBZ{r`>0Pu4q**m=>y! zXid+$g+<-gJx7Qt+XpyQIJG74Qn%cSX;tY?!72>pd;nw?tMu^BwYGyBC zo>?c)E#?ag}%QyaI)dvK7Hl8hrnmNC!n@euQL}JrDVqX1y^#c)iaz!UW}S z%tx(?3RhdvCVE~N<{_d*FBb9voqVI{^rfR6FdX%@cvu@7hW23oM-W8atDO-V)AN!W z1y4~L{o^E4A+k3TrQVK0y`+x!Ug$4Gvf({F#AjBOoC2@7wg4o0_#C)TmYg;`R6YBe z4LJ9qAq{^)NQX6Sb=}K=v$#bKX*@s(d&9XsM<<2{U(0qDeJvtbjr_%jqc<=hlIs!oN<}{J zjLvLQn6Q|*V=^8H7*l#O8z!gP4)l3wxk}zlvwxVmX}h;I#am+aoU`Uq(}!S-e;Ep~ zc~jxm&G*D~+2&2+IuO+E`!+4i($7j5iu~N!J+x4m4 zgre19m?(zNDXwlm`{xqLy>uvtv{*BSW$e(L&Y6juxr6aLZRBhszch(lm$N~(ZjtX6 zSS-|+{s1c328hh&&rNXF9BwaN1GPx6^bvp`3Y-7$PXFINba^Vo%$DIS<9kC5619~4 zisq_?#!6n`#;PwS*9AUl=Xp)K_L}u}QjuVxNhtc;e^BZCt}YL8YDA<%fo{0@h}ozu zkkr!Oy+%R*bOV8fSMX>-F!=1qadA_~o(YvXM0(>d@+RR9h~%(w_Do5aBi;*A1D0 zcez3?)UD4CG4k*FC#EOyGl%%v=Oa*?#a0c-?48HInOJcBdDODhm@0X|fsNTEk&fuM z*98r#XoxBOC~?~W=qPLz3f;B-L*4q`_)~Pv34}0-jx&`;a1kZ|4w0n|zk>36$7En3 z{;U?Z$L*V>r{|zMO`OMeZK#yM=sL0fWgL$g21&R@$1eRO5!O2>w&u3={p-&FnB$1q=^<8Ca6S4PN?W9>Lnq)}GpqfefbUt-JiX)VY$P9Gq~J z1%Uz9tyR$*p|4nT$_G$**b%q{d8^|YrKLWOyH|`p#-L2@%jW?*hhO2sOaC~UAPuK| zQu8eL$uE_4X|U%(2hHyQEFy9ySa$MPJ=LRf(t1#_P?FJ>eKAzA^;>OK6NK79_4hXh z6k1PFsmtVLin;Fylx?h~kY$EtbES$q*Aew5_B>c^@>om!rT&^!G%cJwb{iC5?!I8U zucb1p9y)azWNG!dM@Xu>s%1I?3+PBaZ$iz1MuYp@uKWqmJvV&1YLNW6-JgryWHb>C6*SE!=tMK(Z>HJ z{WgPQYD5cwPHEB$vm>@=AYMo~$XAA<_H~>dg4O7^6_MQc3qxDauP-E_rD1Uw5>p@D z}r4ST3g1{#bt`5&{`L+nG7*jHfqWpL3U!jGSZnq)5GDfp z(2Lc2bW*8m)}h!*R#xir(lNs5_^R--Szf}$QSa8M#R)s0*k~~FijZ(nl%Q06EX96L z!k~K<^S^F!jwkpMN*cZ;9&D0J%$fj45a=e@`0$G+bC#;|1KPgPVz(FC`No$j z8V~NWkdb*S(7Wf$laZ{`(`}S2EhAa`Qa5Z){bV*S3R~03T4a_0@U!2Hqdbqq1QiF;~lOdZ}TtEjo!yt~9+d7;HA(}f6fh3yZ4DuoP z-6N_TAYjq#c4a93xHA-&jQv9iFUDIiPk$eH-L6^y2}g8fh4AKX6DvRDIs_%`&8E*7 z%%~wT1l1>lroY6ujDW9GgwsrPUnRKo!So@L!!e>xeK8D%5Rof5;>xfDni?x<`PQKP z`DELsk7-D4VOdn5TQOiTKK%AhAQ{1GG!!Kk(UYbb3(n_cVs)N1LNE^+{&ZYG!+8+U z6gmwrE9>HYcGW?Es!2c9qbl(AZ~)mjvQgG46(-*)%LRy$pN&i?KF32O9kq5PGv_h}~q5awS0djb9k<9u+d6)^Z0L!O$ zxk5H7(8EWinU8@Xst13*edz{xiroHgUaCyVnY`=}+4!-}0d(;a1w!?7+A^X4@NUp` zM!q}4jnJ_Yp9NXe0d((3ZD9EXEw-?a77m|?&{!if==iIy!IKdPn9@FQ(EIX?_$X@v zUb#o~WR-T~m}5sLLrI$yf2B)r;$g*9JAmo-p_m39n=MLUgf3a_R^_*gU53<^U-HTK zZMwKaf1@FvV5pKgIh-At!{T-{?UHwwduWO>9i{G<+&l{C+%NOSy^z|!(sCepz;y-}Ao60_&prn^5~N2P1$U(|kghC4{v(9oc%-+1ZW&l^Q=%H1Vpyql%ur5fpg$>Zg z#^(cqB5Ef-FH5?=j~BS0ngXjBQgq1Yopf!7?0}=48TyepeSeeaj}0URy2frQTA&=j zx0O-~BQ)qrCYW0~AD{Cd9<$?&3ASe;&9{A$1|em)Lob8^T;~{kOacoXhCH07i@lz| zK3cbJlrI)Zuu>|n8fcl5AgLp9F62Fz^?-7A)cKD7FQ%zUK&X4W|AtEYf}%+2TF0k$ zB~@S#zHdCd=!M8W(U#97>T{SFaZq&KODl*4fS)jg+;6Q!!;O|`9^_GxFQ5nD&uuVr z0}R-5^8jivz}BcBCbj7mNuK;Hm~RsKma*9&%RzgFhZ>U#<}FB+g;RtX?^AWw+MTcc z)m7G(8Q#1h+LopOSEK-xDZQia94{@n*;v!&SP-u}&kL_3PQ%He?EHX>`Er>AS-6VU zty)+AUet)Nm%&QjWVB6X1kEJjaM@NabLTiIGuYlB+qFAr9+7BzKGg>XIVZT|L(&A$ zNMBghyT3_&gZKxc($0m$WH|K9oM!hQt2EM(+QQVU`BoY=`_SpO`mgQQo9+QxF!cgK z)<-Ktcy!2Mt6SwQZXlUwnbJu%0M>d6Q(f1?dB()J}d z+gZr8lYqA@tajy^>Ns6UhK2ie)E-`p>b`IF2u?~wwo2e6KejQ1BTWGaUgmp$fmY5O zr*;ySLp~s=b-BYnc1G8lH&n6-?~eORj}Cofh@i>*10-y0i^KgW2)Kp&wWIYVD;i4x zUvuaLh&0bgmW}h=;VfH7;VXwUzNLerGg>$LT{{3c`lvMUG(|tNtqQ3|9BjYTExSCl zp4~|BU;@0iumI|}aAEL_AEa~gg*N2tBtAYwqu?~~?Drt-Rlb*vJdW1hghi60&~2jY z1#BtMcnLhxIG4ZK7I=iLIDg?k5jRdOY>9{bPI?zS=aAQHzDkVqte%kST- z*lC_YS@tLQX;n64U9(~kb9RCI-88VA7JO612Yn)jl+6|2gBWUD_hKhFkinW2N{XQwm7A?4{So(K z)fh`kiNAl%i1H7mVh#wp@rrwi&U)Eyf$x#e70LeD`) zgZADg{2$d9P)+->jm355n;5nurvxs`tiCZ-*Yv@-l6?tnhSey?4Lxp0!;9?|26Dav zt&|v0NV9mWCj`~F6h2Aw-pNJT2w$kr=#<}avH?aO5Xf8l zy&&4zc!!L$i;6&`$yUmHHc%r&q;or-17}E6ok&v6+f@lW z)ci603-?8iPHQu-=)z=aM}Ka$9o2bZB}o z&mb+jB}jw+Z}1Yk>>IIRo*9=*K#ix!h5cC7tl{BRBqpyO2@1|MDrkj{r!+o%rEv|E zx^^JE-=cJ`?tRI<6H>ia4Xw|jZlqu5om{P&6z=k0Go=p+uwkP5+}sYE(dlZUa(v4s?g>RAPi>-s}iA4i0QHB{C7ScbsUFkhN1H{4ZeM;UtmyR?#T32IBw$D$5PGdv!x8x-px z9G4^@ipV}O1_&eOI>!rah&qux<`wOBbt8OS$CE?!keOidU)P#4E2G{6l6c@uQ zQ^knxoJ5e&zzAH=E+cUn3+pa#l5BKMtGe4I+Iy7f`7Wehg*=Z$*_sV)NXF+EFT9pk z<(RF!^Q>fiT@7Wo);Dhp#5+sBPZQ17@f@HjGfzJ1_}<5$0iV^?aOwB29XV9_^LV#% zUr0bgZDW3rFePc=w|Yt094{@L6Bxw&Y{Gi|wyz!$Gy@6v0$y=rseF|uhi z74B?WTC@$L4BwC1^3H~2-DKaPHqyh3J=Q%DIPzd5Nt)VWOEigNZdeRX9EbTl#0`}2S1lQ%FX^bY0rAtXq!eI-`CI#V92 ziCCn1Um_|By5XbiYm#|3b@ui*5R_~I&yw9BtW`}CS1|S;m3xVOQjp<~AG!me{@IhTP7zwr|4p#TD_{DT ziwiUaD=Xp1J#Tz*7*0^{IEqgK$pxMpN+OOsW5 zhC>=1exRSb(BJqDz(!(h*|QxORJ! zis<20cbhJ3CP{JPC-DqW8MSfcu(E{`9W08s0Xtb+k}{{*1&<` zCA%8~K_|lH2zm7^Hg!M`@7UJRxrVUO7#2JMtT>mhJT8+zGiVEd*i4nB5W8IjRnb$c z7Sn;@L=fts8fwEQaLq%9e3s4gspnnk{$o+@HTeXPvi6t~?@w_rF>gJau3{iz1z zX;04S{LIZtxq~YO*zGUZR8}lT>7@FR-c0x#j|l&^CV45B;tng=VeoTPIfv-$@dF&d3ckc@wWNKIUcRN-qk00gd(mWv<$>iw*0t<=NCqta z?Ft~69#<@Wt409ldX?819wCN(HYt(T1YCq~zB8o*@Us$n%gJC@yW)A-eci;Dmu-1y z8~5{J&XInTrm#7&5gQx=5pNrusRI4piWQz*H}V?kpY8+X0fVGVeml*2_;Zi~(Oi>P zV#jEk$6q0B!mkP#nd{%~$TFQd|CrIn6b=y;)&WA}e+Ta^B5s)WPWJZXa3e+UA`c$~ z$#1a5c91-e8M^-`MmynaP^Pyd(YLg|9>)xnmQ%pa38z%qSL$wLROhV`7+1sTV-ym9U#XCyaR_>qiH3^ z=S8ugQypg_f#;~#kCYU0F3NvwAQbR!Gr4)L7u6JEs2Z|byjMR2eZvK=R!Z;8Ti{7w zMMD5s5(nFI$*Xmt1``t2V1L#6pP?RxP)5xux$nI@dJ!Qe1A+<*9zP2wjA#1W4Ixl) zoYVQ%NBl#K)S*INUqxX{1Zca9#+I6SA9s%CzeU#(i8sM_|#B`t@XV72WI~?RK-M5mFaSgW3I^%J-cyU4S}h9knyfp)A}t^+TzC2P>}%;4 zwx6UTDdh%iAKMth5un`$;e^YPhltmRQ=(Zl8g1PZp}xkvw>dtc`Ko3PhQyx~(H5!4 zDE^WAHo`2!A8uILbF^@?ggl);^1F99%tois<3WM*8xv*(rYYI^o*%Ovs~=G;qv&X# zzr;*o@KmepIxU|Fkf`nbahUit+qSlby_!+7N)BT7FpS}t$nXeo2JhyG%s9w-CeKvG z^qs^^$pYg!(>Y^`r`7cUm%p-3>ZVMJg+SzHX-~eRJX=>xSvl^9(P=^LZE;V+L^gca z3z;vub{eS%Mc*UEZAaUrhE@gBZC`-3rC1|Zy=Qx=HR1`=|25^yGJ&;ezPk~;?Y551 zDT+|F8pzthnK*}i!0h4|B7vPunkG~0K%U-uQz;CwT#RxoQg+TYik4e28owe3Yp_#9p!U-N7B}QdwhnUY%OpIm&_Xqu6*Os1;Tb@nU8j8 z@}0##l}dm7Py)H?p9+3_>%HdT&>|b`ro>1dw->knMIC`>*HMt21FxxA<9rGM8zGFp zwfQv{jcjzVG$K!k8>N%gRYzG!a_dl_(W0`wY%L&$zTZ~^@Es#TVoIc1z>vSm{j-W6 zSgi}iA#rJrXa9JMJC2nZUUjk^*Z%Wkz{-qE3^dz(RogUO`C>vcAMu!O;qLQpNY)a+ zw=i6uvE}dBZ%5W-xwGJio;%fcXCr*T6FpLr#;m`^i*WQxK3u&QGAIzHp*6xvm5c~B zS}*E{TIBL2z-vnt$4^dgYF-U)(#E6i}-wrYWf5r3^0K2u_JY1-bGAyY0_%X=78-*lP z4?4i^#BTmJ(ka3#OjpRy`%==-RV!hs7~HU7S%{stlWo zpQ5_5v}{@ON~b`x5PFW$+LlP)jkq1pH{Ud=DK8F?lOltx5hF|^IVk-S(|iX zsHVowE%aUQJ@AnZEK$K>j<7tUh`9Lo9CCW_(U$XpCym?(YTR;?@?d6F zTMj@1qcq23L5;4=u-bcH9xxM%j_7{jR6Uv)qc6!)uT&*i^Rs=75TUik55S2|Dcx5% z2+h)n#>3i)Md_R9>yiF9$=^P1sM^ik50H3z`<*8|aDs*t@KWJaym;g|DOXgf+jHhs>AJaS=HwdqRPU463Mt8JzB&}h-K_}=!VE3o{u&anarqQOHP*{I-%e2TxuQMLSK9hD*Ii10Fy3{a zMqC+w9P+=8g3@^p0?F>y31>eJkFN8_oJz$v%9$)kMv)Vg(l?!C<#(#Qrhf4^s~!9CsOZj*htI~Lm9h7CdM-Hhs?&Q025BK^=&V@; zLZ=r|joGPQEvn?M!`K)$Z4?S)AtIJBO1})=-s9!6Vy-CHrEEHaz^sDr^U(JioQkE_ zH3hUs8vpxhKTP1N@yFb85bjKwx9rj^1ZKk;Bh$g*$7mqyvcIVfjqzuxJAm0`HhFx8^G@LrgmTrIW+w?oHP&%&3p4E93XSKlOi^t;N zU;zpGQH;h1&FI=Bt8%8w=73VH&HM3E5w^P#S`z=@-D7V==-RU6UV*(UsP-cgv_d+v z=^jgxMu^V*J{1Jjc55fL>g z9!9xV5KkXOb0|<$Gas0?Ku5gDBuhm3^binU%-$NiSko(w7tx^%yP zpw#s(t{L~g72|6Qx=hiNl9~%VArl)_i}rC44*o&AcPI1Oy`uwKj1+|W7uRj*m}gh+M(-Kxj{)&e657unw5JlU}BtKvhXnM#+GYMs-pPA$F`=4 z#qfla=llfwEj>O=xoZdfszPoTe{u{?0C>h2=cGZRs}qVn zUbRL^!@XbYcWQ)>xS-jwQ$U`m6>?41clXic1I|f0*A3;OHFSW6J`Ca0mp2H8pBhS; zqcIDNlF>dX2PzvTnb$U#B_2J3IkEpH$gY(L?M6m;{)Qeu2_09;4dT&JF7#`wy~Gjbls)qtS3L7i`!pfUYF_q`#3yLuG%9=Y=ag27lUKiun<*JpFXN zUp)z9dGn$}kLFW!4z{Y(xir@;_Lpbj#GlPk0~;{_=1OKyNA_N5xE$~&NS3--=w=dC z9cdQyI?T1wBhXO++9I{dnM#mzzvI79W8gw-&~?eUZNfo##Sy}1|MyZGR$XyqV zJ5Ng;z3!jw^aWKLq^3EEySa$gh}ayBzge{Jz^|^CM$@@F%(GRjhF3GKcTwLAjQs&d zyZjn5!NYwhnvlRLDzfU|;;%7KQ1R zqnVnXEH0m0R55C7qgowQS)_rQTT}uce7Wa=@s*^W*8NopSui(QKGk(lk zjTBDr3U{gQ&B}67JvMzq=Q%C^6q6m?Qj6{*M!y;Is&SYGYh!Xu<(AILeXgEQbz#1( zNZON^Tr%4L)H1xDzxUN)o7zRa1ZUrOB)1YH@Xq)@fL|b}%Pm|;!GTP%3Ob*0W?zj< zt4|T-y&1JX^}hL!Jo=8CI#;;x&+{hsPd>h{b!cW`Yy2}^-)CD{kB&3W&;g{PrNVwy z$rewLRL5np+luPq<-Gd*&qbQ(8ei|xdQ+VIR7)TCQuF&Zs7_YQiV&GrLH-{kMj}I) zV9hG_ZxqT#$;QmXMo|YHx2T)Cu+7N4d@Za5Z!fK3s<9OP4_-}l@>((A!l0<%`g4xA z0Skc%-8KhtiQehc#K1I!fT^|DWCg`cTDRoY-+E;6b6m@;=duQEdq$peM5=IH%3*pi z|Fv!TxmmONjQ0Ew;`LoOcq<%hL7FO@fUOsM!9FCGN3FGB{$}Uu=TU@Z=>4)yXFnxx zma+PnLx)WgBy;(9GnE#(L0jvD*zoZWITB`3eFD$vIChI}8aWfF%%b_e-`K$dp~Ma> zLt0}0T;mk<8U=Phd!OU_n9)AzWTi&=bEYs&DqCRM|{HH|2~TATi3z`_ti5xpE#KG zT&WDGCAw~B(B0l^pCHGD{C7QE|kNg4+0?c}MIO51Bww2>|*~dZvtdDf6+CnY*P`L%(PE76v^x~c4n$MAs zrXtoN^5+g5DZC+HP{6bhFmnQ)?$py4%4E}{Xa(FDcx6ow#&uM+E0Krs9l@ zf`Gi?0LplsEi^rGOL-Iip)vQhmtJ4jlLIj)Kew2KAmtg0c{ua1LfhZnesyGV@42GE z4R4cJ24}`azBK@MmXF|hn|`%MC{AD*(kZ>o^*)?P`54pUONaG%7``VhcPSKZRlX)4 z)VpSNK=d7B$%8@bNva(Bo#L~SI8J%$ALW(X`^d9lcMri&*Ksm6`eMjbXm6PM9yVaiFLPSsC{iHNJeZ&tqGzZ2aXcJ z^M1zp)y~+b|6l$puZT^1Ssy@@@ z#g(-96op>&`nqE|THsYWy$VdvF&s2)dv?XY6BrI6lEF5E%aJzt1tz5NZ7CLb@#**q zh5Q9FBnqz*?$+y|tc`;MSdb%*y=>l>i1DAc8J_`nX>ds|%KV3m(vRVlS2YS&Wk{dt zVPB`9V^ni1FU&f@sr=);YQ=u?tRYXcoF=_i-z=69`U?ThIdqo z68A#Q{D=9k4;GT(|4YF|aqA3wrL8X$IIZ)h-GGM^d>3-h^V} zgaaWZoutG6nG&C~S6vJ<45*J(a2by*8KXu^n^pDp=8K1u{&K(C>1)M-sNL_-OKKMZ zh1fShBY?OAJbwvPE&SY7qthrJV2EM>Ie_5-`3FDS;-&DpX!yAI_V*o=G>F#08I*51E@7wIZi*1*vd%(?+4`E;V>A3bq zE4S*)3aq-XL3pl1UWCGdozJs_%Je#Wa7CfZYfpF+5eVMi4Wsq`^ob#c*lD(ayn@?p z5a{KZZ`_B!4S(m}@g>1fBizXmTdxRl_y7ct;ms@fc;7|0TVDv#xMG5wJlvb+dj|)M z(C+-Tl^cTrk|2&Mr_+d5p7U==jzP;e@@h{^aS;jw4ck7ypbpKUEjm(owm(qx=|A7k z3`e$BQpR8Exn@-RAv?3H38vVubzDhvF)VKV!1A!nD;eYsWvfQwMFRufeclNow-c+q zA+iTpzVy{g!&H-rwT6c~7L@u&&65%@e2Ju*lwyB#7AXq3X6b6~*D=g3FT$Ml|F>iH zMIP#RI~s+qIQnOPj? zNzy2Fns48PW=N&S={#Z~biV8>Y(nI~A$zEtbRun!F-71o*#e1Lr`jg=GP5IuT23dWww&wVynYx?3=_9fzQ@9cV-bzdBX!o2{ zt(6ASZrMcP#co{EpfwW~R~7u%-S?sFhF;imy^m+1mmi)qv(cq&Lizk4r$j3`x8ixc zdxx7G=(ng3gha__m~vvDM6Qv5`sO#7kccGE3DAb};nXE}r8C?^X^R%q%Ovns{79fd zZrP2z_pi-SF4%^!JOx{!VdoRuWO15vLS7Lypa+9|swuqD)^T|Xq%Im7(Zd=a`7n{ z7~zlbTBjTFCxluj;}Y7hCxwojNCtH=x(2o|()yNX4( z0(HK7r??V=iL!3_3dMGx2_s=a74xeD+$sILI~Gc0!bt6;tUXYYsmF8x5rE<+g4*BB zG7Y5&NSee}ws6yN)mH2*)y#Y&TmQShXTm2&;_x(gk(=S{pCDIH;LPlA2MX{Y%%UOl z%I$u>MeEHP9Z#2X`t;1mJI;y$x4ueZP8T;@h_inb>Dk_%O>Q<9Dme8Q(i)kZ-_i^i z513}%3=>+EgG>k-zh$s;0gy>G&q#Z6%0MZKayIG)vr$#CC7%=;?(_Y>yBx791_=pF zOnib_66+W=YoPipt(2`Tak^w|CWAj{`uEcp+kQ=0{w-$qWl$CDK65Ox&j^X3 zN6a-QK~xd!(m_FG2sH7&g|aiy#xf9JXX3>CVX)$+#8f?)B=C!1ROV6=d=uGKhVW}B z#R)s0*k~~D>cL5}%B6dL7Gy7;yievO-_GIDl=D%;MNZ?cxSSamEt$-Z(tIb?ZkhIg zQ;2U6C5x(YYoTZ-mPZeIVFr!9Hl%>Zis=7^Y|#!iWI@XyZwqPrIT+eJbN`5}w`93B=yxU{27FbH#QP*rgl&t*;{OV$U1!CJ$br)GmwPFsx7*T%ubu4`dyh~7nz&w%w#o5%DS{E%bM@T?7NG>V9kBE5P{AKCIecJTKR*j-tRQo< z(6W5F#6@siAOFD3W61;>NFx7x^J;WaI5xG$%2g+%GK~aK^fY_;pwWa!x5tzI8F|P` z?UQbrvms3!(e*l?GnjR+Y|*0%&GOZm5R1XK-a4Yj(ZeVl3Ri&`c2|l<6u4s2oN8(g zEd9z!_)~gx(Q%F04;-1P=3ourrS_x|aP|*IYRV~7?a=F9629Z^5ST|5a!alrhyAPg zWoLr8v!f~v8FfU5=0fVo*xrZ-EfYpixw;)sKj8NfTo5J5 zr7(9AH1idy=Sa5dU;h_&=?@oof&|6>vM)bnN3NzFphTG{1hoOebsuneJrL0h;S4ya z3Pn7J0pa(I^%h&`(eBk81Un7I)9=V8qSCN;7F((C=m)y!96*MD`^0SFO6>B}JR`g{ zM|-=G%FH+oD6IxkWN|XKgcsMIWI)s`>rupl$&2|`Sj>)Uuk}UJg^CXT9k-%mudCcU zL5ju7-;g`Wfqew>n##cq=>tl?@op^BjwV^R=6f&cTt|$({hCKT{_;^}Wat<^@ZbN0 zYDY&p1Vum$8Z$N=@BtB)M&J654fc?)XrnJ4TqOnm+bSEE--tjX)WSDYg zr0}Vk1OyqV?1g?)Y0=m4U4m3v-LZ016G9X?w`GXnXm);uVWe99?-?*$f{C{E*ggmP z@ySM6jwIzW0%JpH(#wTVlSdInymlsTk9p-gTk7ds)b=eI(Ivs734}eT{+_RP=>aR@ zB6@)siBFVkKDDw1sebaLhIN9wXDr*zvQL04mxMiYBZxvh94-WQ{O>#JTD$yU_#4r5 zf7ghZw6}_HzG{{FM!iQJBe%=f!k_EhcDNbzGhn(I?;w4iOMGQr(X}U3@@1y=9a5|E zKV9F>Za3TvGAPt8ou9n0(H6sXl(-bp?`8_C$Au7Aflo!*Rp~tB+D9+<9#(~&E%5pt z+`ucsWn~>uwUH-SwB3Gq_;6fovOFKy){4dC=mdnfsdStKfdxrLQ3QK@S4UslISppi z`Sp2<=YA$-US}1vmQN@Cyq-nL^?4G%W(Op=#=N)b>QU3#E-xDBKrxL>8iAH%C^b}Q zJAb08r!kRsJsfo5k=I|%YJJGMiN{Y&FPE){Z8hTr`X3wf>eje$k=1CIXuNEYVlBz^ z70-hf$>@|aZ=GCF=U{jx^)bjvd<8n6rARJ#nKVB9tza^nFjQeUj%|V9D7L65C`=Er zDSKhA4{UpVEp3dbzjXqFMBBH8fRAs@BdS`sdHVU)yQ_!N_6ppDlEN)$wt*O&tJEs}0~Vh^s3*tss+s950IS}A(&s|_4{KtGYho?@9`Oj%3(gwP)$ znZ9I^ZdK{+(=||%D2%Fq&(pxc6lepa0PH1v4BNuEILL4$MsL5TzshyS{40C?Q+kQf zxgQgm6Q&-A?Dbz6b+SGnpO?(3HmBS;q;W&aWZ#>rp^XYQVgVoI9+;E8B zVcz6=d5!uJ`pc#P3wrhJ04?qR4hBMzk78tP3}YezjiWz+))@E+qljj+U~TS92Z~3aPlKLAA#sW1hb+_6dW)@Q{ip>o<+!_g8Z{V zqe=lXM{gj`Ud_bK#_iEC*4Tc;wrJQ9#nf6av&u__k#-$6JLXfg9vk4WI+JdO%)bj*b=5G2o{5H&kwfUx?Zz0 z6%o7_TepG)4+CuwFe$|js;5?36&zY)%sqQVV*J|vuBV^Mn#5Bxnm`0BvJ^7{NB$QY zK-%Zo2%0#p&93lWr;buX+xFxz1_eC-bnIr?p8>ZVS!j%(aa3#Srn|_dc4`vK54lU* zfJ#cP+bj@pSVJyMQhSL5b=(G5?(isO<7qFDcLUZ9JYnoxIxt zSl@yUrh9`+9~9O`#6@K<7p7bRr}_mC993?*C7iQ!1#g^R{JU@4MCzR z%Rnc$euxVRYx`H&He_i)YV2GK_Ab%=RcmlTH_Ib7Vme@d%~f{XpfpGI=(OTlcOw3~ z=e(iwJqA^HqfKEqeOudZ!TFrf!Qth#cC{UHJka;T!`VMN=WQ?UZnZJj`gT>lD$~pu zV|0`DMzSKWTccauP7~D+3^kfDw-$}d$l1-=iR|5N3l<=XSR2?Ze=DST)X*Qi4awOh z;76MGlOZxgAQ!hj$aRJK7J6qh)mduma&<@Ym#0fn!`SZ}$)YfHIsWQ@gM5sxj0z~b zy~P_T7}juv@$r&-FW zt6o4-$aH%=wayC@Jj8{HVTg2yX%A8J_V}>pS=WUV$XG2c0r$P~wu+D@zfwAN8=oT* zLmoUy(Nc>+N(LS`go_>_0zO=(F_%_6bcX!1rFrtO7tZU*zxFZ>V;RE6JC;7(_#6$4 z`*3yrw-|QTny|2OiSE>K#l3n8#|5(&JqrgcEDwSQsm(avB$oYyyTYsi?~ZU=w-7*# zh@eu%X3$Sc=Z4k+w3M}_1`3OrIP?`~F=3`%9QSsBG%H<2xY&^5-Qx8=uR|I>3+&?b zKT`r>0&ujIIeo$u1JE;C$pQctqc3SJUuPZ%E?Eb(pfGI<0 z8I5ZxOzFM_vpqyz7RX36=o42w3MAQ_A;!DY_oC-(h)&Hgw!v%3R9wK@Am~nP)!MX5-fDwN zgs0xbk z(>+KWP+Z0k*H>tIK(ValB-t{7LK1v~TOWLqtu0*&ZSVCUr$F_ANE+xlL1|xIxDTwm za@50W0PIQxKvW({@7-V<xCYj4y*?VVlk$?fYG`%31_}@e+2s);Oj` zk!cvun&w}`oJdyhi_gutcP%e6+g|)fCAtR1@!IyYo3}sh3cSO8kzY8=$fLr^zzE95 zPIcdBn|KHnEo_?SJLAtMkehFXb`lUP#SoX4q5_>LEwI>~?@9b1al1TSN0|fd1|OB? z^)|6jS^N2&Hn6v$%Jl>)G6fC+tt1J!afk8DgQzboI?dBsYY4WTytfC^cjlxoxIuUM z-141W*d@ix6*CCpDsRd*_>ZE#^`9T62xZr3uMR68iWVbeAF9#Ck(6*%JiMvKfs8h4CvbxA zBwGg?9qfQ%jsO`&9X&~`0@NQlJ&Ncw!X8yzyWe4+MT*qsr>Fm5W z?wOVjU4Z-(l{*CCo{~hn60m&S^)M$hf0|IP^zQ5-Z%?aWltSs&@m@|O|4!Gy*^l>R z2!%qC#>qiD+R#u-%8POEKqI!}291bl&7w$2!wh@v5`9m87Eg=uFA_L}uh3DTv-?cO zr_f_li8e?;BPN98czJNQJY}Mu2iVT-LR(27!DHbdn{7z+Hl-Uck@Ba?W|dX&5RAH5 zqr8+i!;I$Ae5qgKxkiTKKr4lhpNAqHej0gIt5gR}f?yuH9RGw49PE-4Z)0n$K)MJs4)X|1rvHh<90O0={raSo@XrD`pb zM$p9`+gk(~MJm9-8;IoH!Csio=k<^jdLNQ^YjNJp4iF$lcn@lU^1r5wGn4?#dRV^( zwWBbk^~@r~7?Fjr_5H6z!?S@&fnpz;*C_VUg_e1**Z@Ivvn06QDQ~leJ7BapT`NJF z(%O{16DAbbDn>BN*#K;Pw8-5-pWsh!<*n}wEHeXScT@DpJAOf-O7i4OXi!Bh^YANl z2*@=&(Wll9Xcl4It3Ij*!!bCbaPIkZiXAbm%7WU{lpI zwNAbDopp83@0BuKB>M<%JP}6_xKQk~|NMMx{pG!Gl<^XJRN&LDAgZ+k(n=xof|xs#?ax1i}|Ifd>}#O{HY%+z7Jjb@c2@g%wgw^DBIgH ze0f;btn_+p!?kSLd-s^N3EuK#%L3xoO{H`0Q|PT(V^rOc)f)2weLZfr zB?m*a1L5cp`O}Mra7d;)I4KeWSz9S7BH;_Ih0#VK7vs>ln9C2PIuk%c;jDchQ zt#azqWo1Xsa$g0Q`A;^#MXfXynqnF){^mf*`Vq#dS3=`n5{+tLeX9i{XL9Lw(O8^s zhoku}tMR7s#>^mBt4xviIh)y#feFhGz9Td~fdR&p9G;!ov_(4Wj@ahYOl=EMwWbDT zlZF7Y@-NlRF9ff3s7H;j4T(S>tFQPR^+k%!rpM)TlnxoLnV_8bfo#8`w)fjFQ=jVz z(rUQ63=CxpvZ}uR^NkJa|Atc!Ceu+RmyWQM-t%$*<0_E3B{inSoz+Tl^x?1kFn3lG z8c6`<6#vv`lasr2iKBKkeXROdwuWurdYqE-eHHU{qB)0V&@xw@68p`8oKAABOKqBd zv<10W^)p;r0_0Opez>Kb+CL!(>l2dY)Fi?z`gDNb(I0CDlVr*RozdvBhe6VG0elCr zLpE4KwdJlGz#Q8Vsql@?{aS|cNOs&+EktvfJ+<466TmM9x&XP74ufFe=>{$ZjXrQv zP3i13JSa^Kv|d~4hIfk*a_{xYvX_-yC^eaYZ*7KeNT#sn_zArg(>@G=eBOcd`|RaP z6jPwd1SMK9IELz(d`5wlO_o=2MkOky>ah&5N>!$ExuBkI>lyDZcZY`6mjZnm1$BMRQQ2j-?`uD9{zgKCN^^}mAs&o>Xi<c%XVQw60^?@hF2gy^MLL>;__xFI_T?UB~A)aSsNXqWt5@-xEG@qFR{%Dec% z^E$KhU|Lsq{Ud@L$QPdZlxNX6eag3&%bYKgzx2swHmJTj@R}57H4fNW^-?2Q^rejz zhNGV0O){*}QVNy$qQb6~Be9L8Xid7vXdz?}Xp;&%z`NLHF` zq5=|tRAhUEGJxOvuLq<*{x5u|Jp)ru4`{*qaEaqP7nEh!1sa5G#n8JqSiLET) zAu*?5^GAr>SQM0Vz7<<8G@!K|1khea7}wbL`Y;5NhUSt&v)sWPsZhE_58zS@5E_EN zXz41Mexvs~*hN~+^E@QND&|dAJ-zM&rQs5*QEO})!e(xPajFHGoB9%;JCWidjg@E~ z2SD)JL4-jhuf(v|SCa+4G>95@xLYDH8WTS-u|%8N2JT@_zpkw^{~F|D`(i5(n&FfhVr5x)>3{1K>}K_W3ah13Iw~m_9AoxP0Q6g%x=_Y zE$g6EzkXYj0~M6C`)=FaXI{n5+ORBOYkxTc^*(}-tzVM%rz`Bl;ON49b!p>zc(1;u ztTJ~Ck_O5crFq;9zaem0$PM0@LQ{=6ni$4I4K$*pZm|@7QLB7exwEphal0OHwGmSB zuT^L7N-LL@)Ow2QR{%-)yDX~Y*p9>l1~3bfaN_DuiA-_HW#I=wv#{j;6u?K3M5b|W zI7FIUK$#uEKW=U#Z;9W#QdQbpVlfrocv}ks%mf;Ej3y|YhkLpwe!|O_P+Gd~aFo>p zam5>D6JFv2Yrk)WxArtVxY7U2S1yZ?QNdJ!>awn@NvX=A2yIQpB>F`rpGk6vGG-fZ zaOkbRlu85fJF&>DJmWUt!r0>_S=0@v1tALntqa|QD8XB71SJziMF%Vx<+z}tC=$7l z%m8Yd_S*yX^@<27L?qXMgFh+~BWm5KgI~q<5D2>?ZmroN7^1Hvzw9 z-z#_7U;HnWNV=90!g=IhfTb8row5e;kIi47=}4V$WU3WHBhU{K*vctK+4egf>6DPQ zoXlJofVv3I-v)UKw;*ca``kddC`dW!i;te}+a@)XgAhLtX~!4(p4OFU_^AB$((eE0 za>AD`^P8hmT_dUM`v>p!7trwsm8!wy z9uY{R?C~%Z(oX26B3xhOD8NyzTfBc)3att-sBG<1 z;-+h9WOz=gh_Q*MxKwrtzWV-*J#}5rp#srdVX6MnEB&-1$^K4kQ;Yz z<^#wA5*l&?IvrSFffn}WB(X}^8PJ7CHil$)D(uuY zhm-^zWB|DMtAQv$1Tv}Y-l)7+k5wp$ScbKOIb1y!zzlDYbH1k=+V_+DyO`9WJQ!_7 z3CO}=zHUF3y!9J~kJU?6%9uetGfG4hX0;4EezaiwDud}Sh5fD7R9f{*<=V;$i<$AM;rd|zf0FX8fxONm08bL&jq*w~(Ne2|v6X~`)YfuAOOyC?R^82!=-3g= zKe@^JdG8mZ$a>TbqusTPjnk7@-xWDb2XhGaL07+q<1R?=H;(!6K(U5t7wmj_QQ&Af z(%E#s@0Y7J4JINBpP-u0i01AhJ0RuEB(L;wPTlRHa`i}c(G>*zKxDcpD>&g8wonO> z08mYL(`T}pVg;%P^U1avgc8$d3PULSW)ex@{QKyj3m=d>Dj>me!r!Z9<@KpsDFk1xz) zh_>iJx{*_$?9T^xYA2t&4`<$QE8T+yT)xlOB1!JdAhipU!B1>a#|Ka){N6u@3T!|v zl}B4p`;nm^=-YhTGp)N%S^%&H;Pf`SJ1lcEI%4B}z{eHYKGLCC zRT_jzJolE)#$Mk)D4Oft6`FDNJso$BfoPJdbWL_^Afnp{3PI4)Bb+6Oy$IJjbLXz( z!W$cw;m^*2)< zsC>I`#WWhPoFVk9`gaIMG|cH3ERr%U=Bn`NF}6fr+XIH#uER38a~|Zhc}zfwU-pZ* zSXSbn1e$F{(dkWqqGd^PQw~|)W_m|z^blWvern_0*})!LLb)P_eal8~t%lTD*kupFzyG#7I( zYT?^WBSbI4X@foIvdqL#tt|OPD=o`0a}nSf(Q*X2L7PSAv|N7*p+k9%7p;ZLUA{9) zcTXsF_^$-R+vCP@uHmh8_CVrYv~*k(cl?LOx-Ek^#}KT?h7#BxpuZI(6-Hn-W67g( zS5M&}EH=;_Nj0ME_I8~6wtAiH*Rdq~>qs+t+0id|jnVdKo&~%@|2b-O|J2b(l34O@ zA;J`D>HPtJC1P3p_;MQjx~(fL+qFUOz^x@SMfSr=$c_+C+6OmQ?g>7-CmaD^=DmoP z#~&!rT4#n@QHMY9sO6>QOubqd;hd*yD_RB&wu*A#Q>XhA137IY%c{7|k$e{mx0HV; zx)YAYAU~ReO^HL$QcDm28V=y<6(OWo~zwDz()$kI9OYOQ~`8j&0TvxTXz9@Oe;R+eWt0(X@8Y7=K z{G!t_%p`NJ9)UgL&kv=S1oY(gr}P!tulnh(j1zdxJZ59psZZUQFmuedd_8pttrg7c zrN^--G6O#rkqWl==fS?9e(?{`*D6SM^RV)tmoQ}4Oe>PNxrt?_k8Un6A_>y0uhywD z*}f*f5-HLKOJiXnd3P{4FAtRnQD*E%q9b-rsNG}pN{0x<_UxtZKf$H7Fd8-j;V z%NU}+HAo-ZAp8a5K3_+eTaSSgug85QP#)P7FXaU{d|Z~fS4Y|$6&Jd#6IA!!w+S_O zgwpJJCx>(CTV&k9@Agmsg;{XQKotyf<2hp$BoJs}?L%bv!!QCm1+&9C z0j`Y(55|~@lbqDYW#9%74eqC`P9?dm(KU;Ig4@O+7zpxqoeRpLNF?ozf6*Xz-7Y!? z%T7>bqeZd}IkH5y(7ikEjC7cYUSgfCQvIf8t7CyeK1CgRR-4=mWUXTLc@vO4m*Psq zK`IExiv;Lv3f%IZ=Ewga0Mq#^WI`qn!E>lq+M02e^0w>wE114l2;XkXb-aGyO;kLg z3fA@G^9lCe0n-J9>JboEs;Lw@JA&5#z^<_;^hcgF4RmHMVpiDZ^{%z)KDHJHMS?I+ z-@*b_W8NBHYtCV2cQBp@!l7;aRfQ$IO-;63HR(XMd*H{#@?QVPdh}m^-82ofbuJwo z7A6_MV_r=Y*?1KJ;!4!D^^YkmZR;t3UahUI79~G+iO#ftUQ`=%|9WkAgax6cOmhQ3 zd$T71ATDc01dK+Wqfi@ljchFBCPQ8jJr4y9i(u(Q1s!6>C&!O@`J-mR(7SlEyBuWs zE*~+pMnO2vp}ueEX!wquV>jb)-s|bt$+H9_LAZM+9xMDM3(3V8`r6TP@)Snf(=V44 z6KK~!x3D}bc}tTJ737Cwt`}ZS^U9M^2puh zL($|sj|(G3%q9;&&V`57?HCl@B+dG3{rd#>0> zkwIqDWM(7%)RZ0I#Cxv%SKyG3$RKfxefG zpsi|R=^KF(15q=Yqyu=&He_WlsUZUMFp_1Jzv~)wb$T=kQ%W>CLA7s1YBOB{pI{s% zcw*v)5u0czo74#%F_DCMDNNAg@o_$axVLo0JP_7kosKNmnuv&8laH8}NQ)uw%m?S) zl?;qzmy`pw-QslP2PEg$`zqEvp9rMB;6uuyCBUgsJY5T_`%pjJIRZyse`KuR7Kr7q zbilPi^!|#jlTq;JmuZInPy>TR_ib2Y?iVJ}W2YW zxz?g-6SyZn7}To!VcqJvsTey(vfwY1=lUvI5tmQX&=ElvNhE=b-rVX2^E!;>Oy!~3 z0@$sF8h5|mB5noXhkGvb63*bm!W3&I9hp#XKSw$j|1h;*kmrfAY^H^qXxX_=qpq7tq1+^*{it8AuBYZ~W7aFv_BKP+nB7VksovzVV?kYLFd;C<=4DF{HZSa}~ME3){g@fQJGQew9kXnYkFZ&MYKr$Zerrq0wwamr{0xZoCO4 z&x3`l0x*NezY!AB>fb^>G@(yhG(mws*sIE!G*TyacHC;O0oXPeIC+Y#K}JY*-*EaJ zOO~zAxkW1xt4(wfiWCTwCM*u=Xn|QLAiN*{$W6iBFeE#4b)?e{u2Hl9(Fj4ius}^w z+M+jSY=4=e(roOQTIuyMb@rUx(?UIo*7Zz=)N@d9h^|C;*aeO>sNO~nczD#4 z2hNL@85A*o44kUn*T;gGhOWL6T6r8FO@hXQ^8$s9;NfSPlIg|do$5hbIkWR#-UN@C z^^|gM@mlr}*4PY?l_}O&ybMWRs#rK()BWsX7B@mQObhrhpQMeZ<~tm8JHNWi(J0@i{p^pt)XyFRHk0IQ`{`K;%W7*)?K_}l+&^5t`pSIXRK-BGyM{A2L^XtD_#a2&tSHI+WCM}TMOngABvXaei?VBqMfDl&7BM^)z>GWnBTwPVxIIq6 ze~2?=0PI^*-^sHddeuZmzw!N)X+3`dP{g z=~zC_cf5Dkx%ebN)0N$NG8uNa`&-26z+~)v^82h!*uluWOB&VbXvFufnn!YtU???` za1$${FGgHzAO0!Gex!@A<*7RE)VSTU)Bv4zo3;XkyFCfJPL7&aS3R&@flO1n!S^+i zGbK1MVxeQO$9#U8`K03Iu~tlbQ2?`);|-|tSf)iYWX|jBD!4CgrJ;cMuEvZ0!EnG1YE%AWyD;kMR>$C$?PtzGfi#_@s$`UwEKfkGD z-@nAizq6Jb`7*nny;P7XB>=<17@d{x=&|aXi6=iDsNoW((t+Pe! z0FUQ=y?2^}9#Q_E?9*GCxDF#5H_^UH(gk)!`vpAN?MXWJcP27!Y$TjJ>zc3>=PKKv z1?w~*410sOzJtdGJsqjHmfN!l^F8?)?s;A5p-^Udcei@_<2N;nqGC^7k~sU<2Br(f zbEn7J!<0SB%o=HZiCl}KsBwf4Ri?Q3X%7`_d6o>SivrnOAX=)T%wya~D4&8tGD_Jy zr5I#yLQFn!fBz^uT)P13_rBE;+LtucBg)W$9jR*BcW_2re{A{J3@CIp8U@*k|#E{2pErA`uMhh*T* ze2yVVj8tCGUyVzz~xB=oUnFmk@kN5;-288XYdlr*JS~urFQ?NR`K`ix!lV?*R=IXi&$Dq zoaE&VtKhQW4YGcvd@gU+#sy-+V}Pz~%mS3w5?fXy=7I$a__1d!E!S#E658$kSXaVN z)n&`)gL*h{2Ow9Y!=bok>PV%F^;(5mfW4?8*&*YHmQ|RfH@p6C?TS9 zu94tG9lo@z-#qS%mHIjTL*2q5?@Kf7Re!5$R^7FB2i) zykyxJQmregwGxDaV&33S?}Z7Qwc#e$ZaR; zUZx>t!D>n+0nxtlqpM3`jW;;hZ0wz{kSSwQ2y1mItYMSaWZDXukteCcs#tvuykD{l zQbQdf z6hG?Y>LK_fhpE$gPq4x99Ol{|Qvo-^m};_cVRQkC9Es|A6YdeKlg3N5c;30CorK4E zLq;?4^qCf)KS~En(!CwJ`P1(8-O;UKn+~&`CN9s3-yjsnl_(O))N9fYFwEQr?|+RX zSSpd5(H2Ld3TGNV`(z#XvA%t?CfHMeQi%`S#X&*OhptyaNOdCfPyRQEyj)$Y$5YSZ zoXrDmZ4u$A;4h!Ud<#7X49q$qsmuA|6SVm&=F2yhAy?mq<@6fLuwlKKES?E-qcnuE zLv^N1=siFIp^9Z2!E3;bKAbYN?ho6BG-L-g$e~Bk?evtA$<%2a8G3%=G_QsUdg{B;Un9EeI=HY7qBwwC^YbkgX)?CI`v`#|8obrxHU?m9J zs3QI?2r8fkLXXvEl@Bp`2~jQSvLV@bDXTcNI&dmAZpwM5vJeqH)!{6LcDy zyrrr2GZSZVd}e-7R#RZ$lSCD8n^frwqK~kn5viJ7>J)6${DuOHEp)a0m+oi!f?KbQ z;;#Sbdaiv3=tww08Z)|Um7}po}*pBCAUs%I%dnNzetVXo`Ho|yC z;$3^01&0@4A10+_#U`09BA+3Vm*!?Tzq5fY2GQvw0~ zf5mJM019Ib!RTMqHI+OXXHw`YRmBU*m=)I34AqD#3AoERndeZMZ51SmM_18YA{VM@DtYfwki(sBv{*WXgtEfj<`%W#uSs^xShJdz$EwJJl%>B~~yS>OEOkvC-3 zjDR72lzBvxIFdqA(K)v~BCfK)kX6u#m&j^h9Z>U{mFHvr3+he5yR4Ot3?AArPdfU7b@%h51t6MLhvwrPla+`4A7MAYoP`fAIR3;S^ zL&O25S6!gqFc6zvBsd#b ze~qzO!SacJUUq;)pS&w-)4M!X4-;*g%_>Fis?$7Q$tGSV?##y%Nbm0spJc@F-YLzw2S$@UPJH z|M6)9nT=@!l+>sKhbG9YA2M5!lk0{ikJ&HROYCpf6LaV47RLmR1lXIuUf-ni3kZheda;C#A)KbNTdR^ zc1gG9Xz@#7Lyw0MbKR0|q<_%cW)1u;m?@@FUh(l)l1%%Q(f*2K@e$+>fcNqC(G?JF(J-;BJfx-QTL`V&9(YXH(&l1qPt?xK~STrFKolJ66}!kGv$ z_$N`tp_>xS8U}%qV*&?{SJFWXwm^*VCPZU!veB?1wb=cRZ}wPWH~ScXmgt2i4jflJ znv<2B&8U(dKFp5zW)EP8osVe#=AdnfG8FZT1oH>(; zZu+(^f-Dj*(iY!xUR#*gwRTRTDsRwZMx%`>DG7hf|%yguxYPv~K zFJ{>fETzgVil#3duSe!K-bd{$SO7K`cSQF!f7qMFgBG%g{Ks^Ec)J$=OP!;7WDe9h z$4a3F1>h5m>kkk Date: Sun, 2 Aug 2026 23:01:41 +0200 Subject: [PATCH 33/43] docs: add more information on the readme --- config-swap/README.md | 65 +++++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/config-swap/README.md b/config-swap/README.md index 5f7e941e..90f8b967 100644 --- a/config-swap/README.md +++ b/config-swap/README.md @@ -26,7 +26,7 @@ noctalia msg panel-toggle tadomika_ari/config-swap:panel ``` The plugin downloads saved configurations from the `Config-Swap-Box` GitHub repository and stores them in `~/Config-Swap`. -Applying a configuration copies its `settings.toml` into `~/.local/state/noctalia/settings.toml` and sets the matching wallpaper. +Applying a configuration copies its `settings.toml` to `~/.local/state/noctalia/settings.toml` and sets the matching wallpaper. Keep a backup of your current configuration before using it. Open the panel from the bar widget, then switch between the two views with `Show Store` and `Show List`. @@ -34,8 +34,7 @@ Open the panel from the bar widget, then switch between the two views with `Show ### Before Start -To use Config Swap, you need to locate your settings.toml file in the `$NOCTALIA_STATE_HOME` directory and add this path to data.json. -It is likely located here: `$NOCTALIA_STATE_HOME/plugins/data/{user}/config-swap/`. For more information, please see the Settings section. +A welcome page is shown after each restart. Agree to continue, or close the plugin if you prefer not to use it. ### Show Store @@ -44,20 +43,22 @@ Use `refresh` to reload the list if nothing appears. Do not spam `refresh`; GitHub rate limits unauthenticated requests. -Each card lets you `install` or `apply` a configuration. +Each card lets you `install` a configuration. +An information panel is also available to explain the store view. | Action | Effect | | --- | --- | -| Refresh | Refresh Github API | -| Install | Install the config in `~/Config-Swap` | -| Apply | Apply Config file | -| Show list | Switch to list | +| Refresh | Refresh the GitHub API data | +| Info | Show information about the store | +| Install | Install the configuration in `~/Config-Swap` | +| Show list | Switch to the list view | ### Show List The list view shows configurations already installed in `~/Config-Swap`. -From there you can `apply` them again or `install` them again to refresh the files. +From there, you can `apply` them again or `delete` them again to refresh the files. +You will be asked to confirm before applying the selected configuration and confirm before deleting the selected configuration. Click a preview image to see extra information such as the author, origin, and description. @@ -65,12 +66,27 @@ Use `Show Store` to return to the GitHub store view. | Action | Effect | | --- | --- | -| Refresh | Refresh Github API | -| Install | Install the config in `~/Config-Swap` | -| Apply | Apply Config file | -| Click the preview | Open extra info | -| Show store | Switch to store | +| Refresh | Refresh the GitHub API data | +| Delete | Delete the configuration in `~/Config-Swap` | +| Apply | Apply the configuration | +| Setting | Open the Config Swap settings | +| Click the preview | Open extra information | +| Show store | Switch to the store view | +### Setting Info + +A settings panel is available. At this time, only saving a configuration is supported. + +You can save your current configuration under a custom name for backup or export. +Saving a configuration copies the current `settings.toml` and creates an `info.json` file, which is important for the plugin and for export to the GitHub store. The `info.json` file can be edited to add information, and you can also add a `preview.png` and a default wallpaper as `wallpaper.png`. +Files are saved in `~/Config-Swap/{name}`. + +This also refreshes the list of installed configurations. + +| Action | Effect | +| --- | --- | +| Input field | Enter a custom name for the save (default: `save`) | +| Save | Create a backup of the current configuration | ### Extra Info @@ -81,20 +97,21 @@ Each configuration should follow the same structure as the downloaded ones, incl If you want to publish a configuration, add it to the `Config-Swap-Box` repository: https://github.com/Tadomika-Ari/Config-Swap-Box Make sure Config Swap is enabled in your plugin list before you try to use it. +You can delete a configuration with the trash button. A warning panel will appear before deletion. +### Contributing -## Settings - -To swap your configuration, you need to set the path to `settings.toml`. -You can find the `data.json` file in `$NOCTALIA_STATE_HOME/plugins/data/{user}/config-swap/` and use this example: +You can contribute to Config Swap! Add your own configuration files and wallpaper to the store so others can use them. +To do so: +* Go to the Settings section and save your configuration. The plugin will copy your `settings.toml` and create an `info.json` file. +* Take a preview image and wallpaper, then go to `~/Config-Swap/{name}` and copy your `preview.png` and `wallpaper.png` files (the exact names are required). +* Update your `info.json` with important information such as the author and description. +* Go to the GitHub page linked from the information panel in the Store section and create a pull request. +* Wait for review, and then your configuration can be shared. -```json -{ - "settingsPath": "path" -} -``` +## Settings -Replace `path` with your personal path. The path is loaded for the next swap. +No setting needed ## Requirements From 82e871c68807fd69e6f6e3c31c1711efa029b0ff Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 2 Aug 2026 23:02:21 +0200 Subject: [PATCH 34/43] fix: rm useless button --- config-swap/config-swap-panel.luau | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index 4607b51a..6f596e23 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -377,7 +377,7 @@ local function renderList() height = previewHeight, fit = "cover", radius = 8, - onClick = "onCloseClicked" + onClick = "" }), ui.row( {gap = 12, align = "start"}, { ui.button({ text = tr("install"), onClick = function() @@ -391,10 +391,6 @@ local function renderList() maxWidth = 120, textAlign = "center", }), - ui.button({ text = tr("apply"), onClick = function() - applyConfig(data) - end - }), } ), })) end @@ -516,8 +512,8 @@ local function renderInstall() end }), ui.row( {gap = 12, align = "start"}, { - ui.button({ text = "install", onClick = function() - downloadConfig(data) -- here + ui.button({ text = "delete", onClick = function() + renderDeleteConfirmation(data.name) end }), ui.label({ From 9e030323151323291663f318d7146f4ec8480a34 Mon Sep 17 00:00:00 2001 From: Lucas Date: Sun, 2 Aug 2026 23:40:16 +0200 Subject: [PATCH 35/43] docs: add comment --- config-swap/config-swap-panel.luau | 77 ++++++++++++++++-------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index 6f596e23..e4795fc2 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -1,7 +1,7 @@ --!nonstrict --- Config Swap +-- Config Swap panel --- Local variable +-- Local variables local configBox = "~/Config-Swap" @@ -13,35 +13,35 @@ local previewWidth = 470 local previewHeight = 350 local previewImage = "" -local selectedView = 0 +local selectedView = 0 -- Controls which screen to render (0=welcome, 1=installed, 2=store) -local previewCache = {} +local previewCache = {} -- In-memory map: config name -> cached preview image path local previewPending = {} -local configWidth = 800 +local configWidth = 800 -- Preview dialog size local configHeight = 600 --- Type function +-- Data model type infoConfig = { - name: string, -- name config - author: string, -- author config - origin: string, -- origin congig (Nixos, Arch, CachOS,..) - preview: string, -- preview config (path to preview.png) - path: string, -- config path (example: config/{name}) - description: string, -- little description - wallpaperPath: string, -- wallpaper path - depedencie: boolean, -- other depedencie for your config + name: string, -- Configuration name + author: string, -- Configuration author + origin: string, -- Distribution/source (NixOS, Arch, CachyOS, etc.) + preview: string, -- Preview image path (preview.png) + path: string, -- Config path (example: config/{name}) + description: string, -- Short description + wallpaperPath: string, -- Wallpaper path + depedencie: boolean, -- Whether extra dependencies are required } --- Section for state dir +-- Resolve Noctalia state directory local function resolveStateDir() return noctalia.getenv("NOCTALIA_STATE_HOME") or ((noctalia.getenv("XDG_STATE_HOME") or ((noctalia.getenv("HOME") or "") .. "/.local/state")) .. "/noctalia") end --- section for get all config +-- Store listing cache local allConfig: { infoConfig } = {} @@ -52,7 +52,7 @@ local requestFetchList = { follow_redirects = true } --- translate section +-- Translation helper function tr(key, subst) if subst then @@ -61,7 +61,7 @@ function tr(key, subst) return noctalia.tr("panel." .. key) end --- function get position of settings noctalia +-- Build settings.toml path from the resolved state dir function takePosSetting() local noctaliaStateDir = resolveStateDir() @@ -69,16 +69,16 @@ function takePosSetting() return settingsTomlPath end --- Section protection shell commande +-- Shell escaping to safely build external commands local function shellQuote(s) return "'" .. tostring(s):gsub("'", "'\\''") .. "'" end --- Section for Every Warning +-- Welcome warning screen -local function welcomeWarning() +local function welcomeWarning() -- Shown after startup; refusing closes the plugin panel.render(ui.column({ flexGrow = 1, gap = 16 }, { ui.row({ align = "center", justify = "space_between", gap = 8 }, { ui.label({ text = tr("title"), textAlign = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), @@ -97,7 +97,7 @@ local function welcomeWarning() })) end -local function renderConfirmApply(data: infoConfig) +local function renderConfirmApply(data: infoConfig) -- Confirmation dialog before applying a config panel.render(ui.column({ flexGrow = 1, gap = 16 }, { ui.row({ align = "center", justify = "space_between", gap = 8 }, { ui.label({ text = tr("title"), textAlign = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), @@ -116,7 +116,7 @@ local function renderConfirmApply(data: infoConfig) })) end --- apply config file and wallpaper +-- Apply selected config and wallpaper function applyConfig(data: infoConfig) noctalia.notify(tr("apply_notification_title")) @@ -140,7 +140,7 @@ function applyConfig(data: infoConfig) end) end --- download config folder (support: noctalia settings, preview image and json info +-- Download the full config bundle: settings, info json, preview, wallpaper function downloadConfig(data: infoConfig) noctalia.notify(tr("install_notification_title")) @@ -185,15 +185,17 @@ function downloadConfig(data: infoConfig) end) end --- Section Preview for github store +-- GitHub store preview cache local function previewCachePath(name) + -- Keep cached previews under plugin data to avoid repeated downloads. local dir = noctalia.pluginDataDir() .. "/preview-cache" noctalia.mkdirAll(dir) return dir .. "/" .. name .. ".png" end local function queuePreviewDownload(name) + -- Prevent duplicate requests for the same preview while one is in flight. if previewCache[name] ~= nil or previewPending[name] then return end @@ -216,7 +218,7 @@ local function queuePreviewDownload(name) end) end --- Section delete install file +-- Delete an installed config local function delete(name) @@ -226,7 +228,7 @@ local function delete(name) end local target = noctalia.expandPath(configBox .. "/" .. name) - local cmd = "rm -rf " .. shellQuote(target) + local cmd = "rm -rf " .. shellQuote(target) -- shellQuote protects the rm target path noctalia.runAsync(cmd, function(result) if result and result.exitCode == 0 then noctalia.notify("Delete Ok") @@ -237,7 +239,7 @@ local function delete(name) render() end -local function renderDeleteConfirmation(name) +local function renderDeleteConfirmation(name) -- Confirmation panel before deleting a config panel.render(ui.column({ flexGrow = 1, gap = 16 }, { ui.label({ text = tr("confirmation_delete") .. name .. "?", align = "center", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), ui.button({ text = tr("delete"), onClick = function() @@ -248,10 +250,10 @@ local function renderDeleteConfirmation(name) })) end --- Section for setting and reset / save config +-- Save/backup settings section local noctaliaStateDir = resolveStateDir() -local settingsTomlPath = noctaliaStateDir .. "/settings.toml" +local settingsTomlPath = noctaliaStateDir .. "/settings.toml" -- Active settings file path local saveNameInput = "save" @@ -317,7 +319,7 @@ local function renderSetting() })) end --- Section render for github Store +-- Store information panel local function renderInfoStore() panel.render(ui.column({ flexGrow = 1, gap = 16 }, { @@ -424,7 +426,7 @@ local function renderList() })) end --- Section render for see config install info +-- Installed config details panel local function renderConfig(data: infoConfig) local rows = { @@ -450,7 +452,7 @@ local function renderConfig(data: infoConfig) })) end --- Section for list allerady install file +-- List installed configs from ~/Config-Swap function getListInstall() local localPath = configBox @@ -463,6 +465,7 @@ function getListInstall() local data = content and noctalia.json.decode(content) or nil if not data then + -- Fallback entry when info.json is missing or invalid. table.insert(listShow, { name = entryName, path = "config/" .. entryName, @@ -490,7 +493,7 @@ function getListInstall() return listShow end --- section for render all installed config +-- Render installed configs grid local function renderInstall() pickSlots = {} @@ -559,7 +562,7 @@ local function renderInstall() })) end --- Render Choice +-- Root view router function render() if selectedView == 0 then @@ -573,7 +576,7 @@ function render() end end --- Get all config on github +-- Fetch available configs from GitHub function getFetchList(onDone) allConfig = {} @@ -604,7 +607,7 @@ function getFetchList(onDone) end) end --- Other function +-- Plugin entry points function onOpen(_context) render() From a6239bfd519cdd5cb0a429cd97e8d0bf67ce361f Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 3 Aug 2026 00:11:24 +0200 Subject: [PATCH 36/43] fix: add depedencie missing --- config-swap/README.md | 2 ++ config-swap/plugin.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/config-swap/README.md b/config-swap/README.md index 90f8b967..6f0dfe79 100644 --- a/config-swap/README.md +++ b/config-swap/README.md @@ -7,6 +7,7 @@ A [Noctalia](https://github.com/noctalia-dev/noctalia) v5 plugin for switching b For this plugin, you need: * internet access to fetch configuration data from GitHub on the store page * `cp` to apply a configuration +* `rm` to delete the selected installed configuration (not the current configuration) ## Plugin @@ -117,6 +118,7 @@ No setting needed - Noctalia ≥ 5.0.0 - `cp` +- `rm` - internet access for the store view ## Install diff --git a/config-swap/plugin.toml b/config-swap/plugin.toml index 7d5ede44..489d94ca 100644 --- a/config-swap/plugin.toml +++ b/config-swap/plugin.toml @@ -4,7 +4,7 @@ version = "1.0.0" plugin_api = 4 author = "TadomiKa-Ari" license = "MIT" -dependencies = ["cp"] +dependencies = ["cp", "rm"] icon = "file" description = "A Noctalia widget for applying Configuration file with one click" tags = ["utility"] From d47c408a08eddfa0b4df4e399d5cc1bb75cb2b62 Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 4 Aug 2026 10:17:59 +0200 Subject: [PATCH 37/43] fix: rm death code --- config-swap/config-swap-service.luau | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/config-swap/config-swap-service.luau b/config-swap/config-swap-service.luau index 142723f1..ee8a9f76 100644 --- a/config-swap/config-swap-service.luau +++ b/config-swap/config-swap-service.luau @@ -1,17 +1 @@ -noctalia.mkdirAll("~/Config-Swap") - - -type data = { - settingsPath: string -} - -local dataInsert: data = { - settingsPath = "" -} - -local dir = noctalia.pluginDataDir() -local path = dir .. "/data.json" - -if not noctalia.fileExists(path) then - noctalia.writeFile(path, noctalia.json.encode(dataInsert)) -end \ No newline at end of file +noctalia.mkdirAll("~/Config-Swap") \ No newline at end of file From 88aea7c886a47049129cae2d4bad9605c7e26a45 Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 4 Aug 2026 10:20:36 +0200 Subject: [PATCH 38/43] fix: rm update interval not use --- config-swap/config-swap-widget.luau | 1 - 1 file changed, 1 deletion(-) diff --git a/config-swap/config-swap-widget.luau b/config-swap/config-swap-widget.luau index 019067e7..e9b9d62a 100644 --- a/config-swap/config-swap-widget.luau +++ b/config-swap/config-swap-widget.luau @@ -1,5 +1,4 @@ function update() - noctalia.setUpdateInterval(1000) barWidget.setGlyph("file") end From a98dc837ab56541d98cdf4df1ea40736ef3133dd Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 4 Aug 2026 11:05:27 +0200 Subject: [PATCH 39/43] feat!: add delete, save and apply protection, reject invalid name --- config-swap/config-swap-panel.luau | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index e4795fc2..8a371943 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -41,6 +41,18 @@ local function resolveStateDir() or ((noctalia.getenv("XDG_STATE_HOME") or ((noctalia.getenv("HOME") or "") .. "/.local/state")) .. "/noctalia") end +-- Detecte dangerous charactère + +function isDetecte(target: string) + local match_string = "[/.]" + + if string.match(target, match_string) then + return true + else + return false + end +end + -- Store listing cache local allConfig: { infoConfig } = {} @@ -121,6 +133,11 @@ end function applyConfig(data: infoConfig) noctalia.notify(tr("apply_notification_title")) + if isDetecte(data.name) == true then -- detecte malicious name + noctalia.notify(tr("apply_notification_title"), "Invalid name. Please check your info.json") + return + end + local pathSettings = takePosSetting() if not pathSettings then noctalia.notify(tr("notification_title"), "Path not found, please check your setting toml") @@ -227,6 +244,11 @@ local function delete(name) return end + if isDetecte(name) == true then -- detecte malicious name + noctalia.notify("Delete", "Invalide Name. Please check info.json") + return + end + local target = noctalia.expandPath(configBox .. "/" .. name) local cmd = "rm -rf " .. shellQuote(target) -- shellQuote protects the rm target path noctalia.runAsync(cmd, function(result) @@ -259,6 +281,12 @@ local saveNameInput = "save" local function saveConfig() local name = saveNameInput ~= "" and saveNameInput or "save" + + if isDetecte(name) then -- detecte special charactere + noctalia.notify("Save", "Invalid Name") + return + end + local savePath = configBox .. "/" .. name noctalia.mkdirAll(savePath) From 305e81b6ded832175851768f382794c373b17575 Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 4 Aug 2026 11:45:12 +0200 Subject: [PATCH 40/43] docs: add more info in info section --- config-swap/config-swap-panel.luau | 6 +++++- config-swap/translations/en.json | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index 8a371943..98bfaffd 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -377,6 +377,10 @@ local function renderInfoStore() ui.label({ text = tr("info_store_community_title"), fontSize = 13, fontWeight = "bold", color = "on_surface" }), ui.label({ text = tr("info_store_community_body"), fontSize = 12, color = "on_surface_variant" }), }), + ui.column({ gap = 4 }, { + ui.label({ text = tr("info_how_to_submite_config_title"), fontSize = 13, fontWeight = "bold", color = "on_surface" }), + ui.label({ text = tr("info_how_to_submite_config_body"), fontSize = 12, color = "on_surface_variant" }), + }), ui.column({ gap = 4 }, { ui.label({ text = tr("info_store_rate_limits_title"), fontSize = 13, fontWeight = "bold", color = "on_surface" }), ui.label({ text = tr("info_store_rate_limits_body"), fontSize = 12, color = "on_surface_variant" }), @@ -443,7 +447,7 @@ local function renderList() render() end }), - ui.button({ text = "info", onClick = function() + ui.button({ text = "Info", onClick = function() renderInfoStore() end }), diff --git a/config-swap/translations/en.json b/config-swap/translations/en.json index a917372f..5c48dc57 100644 --- a/config-swap/translations/en.json +++ b/config-swap/translations/en.json @@ -38,8 +38,10 @@ "info_store_applying_title": "Applying a profile", "info_store_applying_body": "Pressing apply replaces your active settings.toml and wallpaper with the selected profile. This is not reversible unless you saved a backup first from the Setting section.", "info_store_community_title": "Community content", - "info_store_community_body": "Profiles are submitted by community members and are review by owner.", + "info_store_community_body": "Profiles are submitted by community members and are reviewed by the owner.", "info_store_rate_limits_title": "Rate limits", - "info_store_rate_limits_body": "The store list is fetched from the GitHub API, which limits unauthenticated requests. Avoid refreshing repeatedly in a short period of time." + "info_store_rate_limits_body": "The store list is fetched from the GitHub API, which limits unauthenticated requests. Avoid refreshing repeatedly in a short period of time.", + "info_how_to_submite_config_title": "How to submit a configuration", + "info_how_to_submite_config_body": "You can contribute to Config Swap by adding your own configuration files and wallpaper to the store so others can use them.\n\nTo do so:\n* Go to the Settings section and save your configuration. The plugin will copy your settings.toml and create an info.json file.\n* Take a preview image and wallpaper, then go to ~/Config-Swap/{name} and copy your preview.png and wallpaper.png files. The exact names are required.\n* Update your info.json with important information such as the author and description.\n* Go to the GitHub page linked from the information panel in the Store section and create a pull request.\n* Wait for review, and then your configuration can be shared." } } \ No newline at end of file From c3cdfceb92bb19fc2aca89bc05f5073336c8ec8d Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 4 Aug 2026 11:55:59 +0200 Subject: [PATCH 41/43] feat!: add more charactere in match string in isDetect function --- config-swap/config-swap-panel.luau | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index 98bfaffd..a11e18af 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -44,7 +44,7 @@ end -- Detecte dangerous charactère function isDetecte(target: string) - local match_string = "[/.]" + local match_string = "[/.$();]" if string.match(target, match_string) then return true From 0a907811ce8360846539c734584b1454b0ca63dd Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 4 Aug 2026 11:57:43 +0200 Subject: [PATCH 42/43] fix: change wrong name function --- config-swap/config-swap-panel.luau | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index a11e18af..7b36415f 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -43,7 +43,7 @@ end -- Detecte dangerous charactère -function isDetecte(target: string) +function isDetect(target: string) local match_string = "[/.$();]" if string.match(target, match_string) then @@ -133,7 +133,7 @@ end function applyConfig(data: infoConfig) noctalia.notify(tr("apply_notification_title")) - if isDetecte(data.name) == true then -- detecte malicious name + if isDetect(data.name) == true then -- detecte malicious name noctalia.notify(tr("apply_notification_title"), "Invalid name. Please check your info.json") return end @@ -244,7 +244,7 @@ local function delete(name) return end - if isDetecte(name) == true then -- detecte malicious name + if isDetect(name) == true then -- detecte malicious name noctalia.notify("Delete", "Invalide Name. Please check info.json") return end @@ -282,7 +282,7 @@ local saveNameInput = "save" local function saveConfig() local name = saveNameInput ~= "" and saveNameInput or "save" - if isDetecte(name) then -- detecte special charactere + if isDetect(name) then -- detecte special charactere noctalia.notify("Save", "Invalid Name") return end From 02465c83c3afd1c39b7f7d5d1bfddf009923a200 Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 6 Aug 2026 00:24:42 +0200 Subject: [PATCH 43/43] feat!: add protection for invalid name in dowload Config --- config-swap/config-swap-panel.luau | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config-swap/config-swap-panel.luau b/config-swap/config-swap-panel.luau index 7b36415f..47071a22 100644 --- a/config-swap/config-swap-panel.luau +++ b/config-swap/config-swap-panel.luau @@ -162,6 +162,11 @@ end function downloadConfig(data: infoConfig) noctalia.notify(tr("install_notification_title")) + if isDetect(data.name) == true then + noctalia.notify("Download", "Invalide name detected, please check target config") + return + end + noctalia.mkdirAll(configBox .. "/" .. data.name) local path = data.path .. "/settings.toml"