feat!: add W Engine plugin (#81)
* feat!: add W Engine plugin * feat!: add en translation * fix: rm useless line * feat!: add preview window in a pannel * fix: rm no dependencies tools * feat!: update thumbnail with new image * feat: add screen choose with selector and add id check for warn string command folder * docs: add dependencies on toml and read me * fix: rm pkill and use kill * feat!: add multiple path check, default output, select where set wallpaper * feat!: rm debug notif * docs: add new action documentation * feat!: add new service for good restart (pkill all instance linux-wallpaper and rm all tmp file created by w engine) * feat!: add check if pid is a linux-wallpaper instance * docs: add depedencie and new entries * feat!: support personnal path with json load * docs: add docs for custom path * feat!: use new api function for show all wallpaper * fix: rm useless notify
This commit is contained in:
@@ -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.
|
||||
@@ -0,0 +1,24 @@
|
||||
id = "tadomika_ari/w-engine"
|
||||
name = "W Engine"
|
||||
version = "1.0.0"
|
||||
plugin_api = 3
|
||||
author = "TadomiKa-Ari"
|
||||
license = "MIT"
|
||||
dependencies = ["linux-wallpaperengine", "kill", "setsid"]
|
||||
icon = "movie"
|
||||
description = "A Noctalia widget for applying Wallpaper Engine wallpapers from Steam Workshop with one click"
|
||||
tags = ["wallpaper", "utility"]
|
||||
|
||||
[[widget]]
|
||||
id = "w-engine-widget"
|
||||
entry = "w-engine.luau"
|
||||
|
||||
[[panel]]
|
||||
id = "w-engine-panel"
|
||||
entry = "w-engine-panel.luau"
|
||||
width = 800
|
||||
height = 600
|
||||
|
||||
[[service]]
|
||||
id = "start"
|
||||
entry = "start.luau"
|
||||
@@ -0,0 +1,11 @@
|
||||
-- reset section
|
||||
|
||||
noctalia.runAsync("pkill linux-wallpaper")
|
||||
local entries = noctalia.listDir("/tmp")
|
||||
for _, entrieName in entries do
|
||||
if entrieName:match("^w%-engine") then -- match for w-engine tmp file
|
||||
noctalia.removeFile("/tmp/" .. entrieName)
|
||||
end
|
||||
end
|
||||
|
||||
-- realod section
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"plugin_name": "W Engine",
|
||||
"plugin_description": "A widget for Wallpaper Engine. Set wallpaper from Wallpaper Engine with simple click.",
|
||||
"panel": {
|
||||
"apply": "Apply wallpaper {id}",
|
||||
"list_failed": "Could not list Wallpaper Engine wallpapers",
|
||||
"missing_project_json": "No project.json for {name}",
|
||||
"no_wallpapers": "No Wallpaper Engine wallpapers found",
|
||||
"title": "W Engine"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
--!nonstrict
|
||||
-- W-Engine panel — picks a Wallpaper Engine workshop item and applies it via
|
||||
-- linux-wallpaperengine.
|
||||
|
||||
-- Local variable
|
||||
|
||||
wallpaperPath = {"~/.steam/steam/steamapps/workshop/content/431960/",
|
||||
"~/.local/share/Steam/steamapps/workshop/content/431960/",
|
||||
"~/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/workshop/content/431960/",
|
||||
"~/snap/steam/common/.local/share/Steam/steamapps/workshop/content/431960/",
|
||||
"~/snap/steam/common/.local/share/steam/steamapps/workshop/content/431960/"}
|
||||
wallpaperPathSelected = nil
|
||||
|
||||
local pickSlots = {}
|
||||
local columns = 4
|
||||
local previewWidth = 180
|
||||
local previewHeight = 100
|
||||
|
||||
local outputFinalList = {}
|
||||
local outputSelectedName = nil
|
||||
local lastOutputUse = nil
|
||||
|
||||
local pidList: { killSystem } = {}
|
||||
|
||||
-- load personnal path
|
||||
|
||||
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.personnalPath then
|
||||
for _, p in ipairs(data.personnalPath) do
|
||||
table.insert(wallpaperPath, p)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
type Wallpaper = {
|
||||
nb: string,
|
||||
name: string,
|
||||
preview: string,
|
||||
}
|
||||
|
||||
type killSystem = {
|
||||
pid: number,
|
||||
output: string,
|
||||
}
|
||||
|
||||
function tr(key, subst)
|
||||
if subst then
|
||||
return noctalia.tr(key, subst)
|
||||
end
|
||||
return noctalia.tr(key)
|
||||
end
|
||||
|
||||
function takeWallpaper()
|
||||
local wallpaper: { Wallpaper } = {}
|
||||
|
||||
for count = 1, #wallpaperPath, 1 do
|
||||
if noctalia.listDir(wallpaperPath[count]) then
|
||||
wallpaperPathSelected = wallpaperPath[count]
|
||||
end
|
||||
end
|
||||
|
||||
local entries, err = noctalia.listDir(wallpaperPathSelected)
|
||||
if not entries then
|
||||
warn(err or "impossible de lister")
|
||||
noctalia.notify(tr("panel.title"), tr("panel.list_failed"))
|
||||
return wallpaper
|
||||
end
|
||||
for _, entryName in entries do
|
||||
|
||||
if not tonumber(entryName) then -- test if is a id
|
||||
continue
|
||||
end
|
||||
|
||||
local content, readErr = noctalia.readFile(wallpaperPathSelected .. entryName .. "/project.json")
|
||||
if content then
|
||||
local value = noctalia.json.decode(content)
|
||||
local name = value.title or entryName
|
||||
local preview = value.preview or "no preview"
|
||||
table.insert(wallpaper, {
|
||||
nb = entryName,
|
||||
name = name,
|
||||
preview = preview,
|
||||
})
|
||||
else
|
||||
noctalia.notify(tr("panel.title"), tr("panel.missing_project_json", { name = entryName }))
|
||||
end
|
||||
end
|
||||
return wallpaper
|
||||
end
|
||||
|
||||
function trim(s) -- personnal trim
|
||||
return (s:gsub("^%s+", ""):gsub("%s+$", ""))
|
||||
end
|
||||
|
||||
function isValidPid(screen, pid)
|
||||
local path = "/tmp/w-engine-" .. screen .. ".pid"
|
||||
if noctalia.fileExists(path) then
|
||||
local fileTmp = noctalia.readFile(path)
|
||||
if not fileTmp then
|
||||
return false
|
||||
end
|
||||
local file = trim(fileTmp)
|
||||
if file == pid then
|
||||
|
||||
local check = "/proc/" .. pid .. "/cmdline"
|
||||
local checkContentTmp = noctalia.readFile(check)
|
||||
if not checkContentTmp then
|
||||
return false
|
||||
end
|
||||
local checkContent = trim(checkContentTmp)
|
||||
local match = "^linux%-wallpaperengine"
|
||||
if checkContent:match(match) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function getStoredPid(screen)
|
||||
for _, entry in ipairs(pidList) do
|
||||
if entry.output == screen then
|
||||
if isValidPid(screen, entry.pid) then
|
||||
return entry.pid
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function setStoredPid(screen, pid)
|
||||
for _, entry in ipairs(pidList) do
|
||||
if entry.output == screen then
|
||||
entry.pid = pid
|
||||
return
|
||||
end
|
||||
end
|
||||
table.insert(pidList, { pid = pid, output = screen })
|
||||
end
|
||||
|
||||
|
||||
function launchWallpaper(screen, id, pidFile)
|
||||
local cmd = "setsid linux-wallpaperengine --screen-root " .. screen .. " " .. id
|
||||
.. " > /dev/null 2>&1 & echo $! > " .. pidFile
|
||||
|
||||
noctalia.runAsync(cmd, function(result)
|
||||
local pidContent = noctalia.readFile(pidFile)
|
||||
if pidContent then
|
||||
local pid = trim(pidContent)
|
||||
setStoredPid(screen, pid)
|
||||
end
|
||||
end, 5000)
|
||||
end
|
||||
|
||||
function loadWallpaper(id)
|
||||
lastOutputUse = outputSelectedName or takeOutput()
|
||||
local pidFile = "/tmp/w-engine-" .. lastOutputUse .. ".pid"
|
||||
local oldPid = getStoredPid(lastOutputUse) -- here
|
||||
if oldPid then
|
||||
noctalia.runAsync("kill " .. oldPid, function() -- here
|
||||
launchWallpaper(lastOutputUse, id, pidFile)
|
||||
end, 3000)
|
||||
else
|
||||
launchWallpaper(lastOutputUse, id, pidFile)
|
||||
end
|
||||
end
|
||||
|
||||
function onPickAt(slot)
|
||||
local id = pickSlots[slot]
|
||||
if id ~= nil then
|
||||
loadWallpaper(id)
|
||||
noctalia.notify(tr("panel.title"), tr("panel.apply", { id = id }))
|
||||
end
|
||||
end
|
||||
|
||||
local function render()
|
||||
pickSlots = {}
|
||||
local tiles = {}
|
||||
local list = takeWallpaper()
|
||||
for count = 1, #list, 1 do
|
||||
local data = list[count]
|
||||
local slotIndex = count - 1
|
||||
pickSlots[slotIndex] = data.nb
|
||||
table.insert(tiles, ui.column({ gap = 4, key = data.nb, align = "center" }, {
|
||||
ui.image({
|
||||
path = wallpaperPathSelected .. data.nb .. "/" .. data.preview,
|
||||
width = previewWidth,
|
||||
height = previewHeight,
|
||||
fit = "cover",
|
||||
radius = 8,
|
||||
onClick = function()
|
||||
loadWallpaper(pickSlots[slotIndex])
|
||||
noctalia.notify(tr("panel.title"), tr("panel.apply", { id = pickSlots[slotIndex] }))
|
||||
end,
|
||||
}),
|
||||
ui.label({
|
||||
text = data.name,
|
||||
fontSize = 11,
|
||||
maxLines = 1,
|
||||
maxWidth = 120,
|
||||
textAlign = "center",
|
||||
}),
|
||||
}))
|
||||
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 = getOutput(), onChange = "outputSelected" }),
|
||||
ui.button({ glyph = "close", onClick = "onCloseClicked" }),
|
||||
}),
|
||||
ui.scroll({ gap = 12, align = "stretch", flexGrow = 1 }, rows),
|
||||
}))
|
||||
end
|
||||
|
||||
function takeOutput()
|
||||
local output = noctalia.outputs()
|
||||
for _, outputF in ipairs(output) do
|
||||
return outputF.name
|
||||
end
|
||||
end
|
||||
|
||||
function outputSelected(index, label)
|
||||
if type(label) == "string" and label ~= "" then
|
||||
outputSelectedName = label
|
||||
noctalia.notify("Output change :", outputSelectedName)
|
||||
end
|
||||
end
|
||||
|
||||
function getOutput()
|
||||
outputFinalList = {}
|
||||
local outputsList = noctalia.outputs()
|
||||
if not outputsList then
|
||||
return
|
||||
end
|
||||
for _, output in ipairs(outputsList) do
|
||||
noctalia.log(output.name)
|
||||
table.insert(outputFinalList, output.name)
|
||||
end
|
||||
return outputFinalList
|
||||
end
|
||||
|
||||
function onOpen(_context)
|
||||
outputSelectedName = takeOutput()
|
||||
render()
|
||||
end
|
||||
|
||||
function onCloseClicked()
|
||||
panel.close()
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
function update()
|
||||
noctalia.setUpdateInterval(1000)
|
||||
barWidget.setGlyph("movie")
|
||||
end
|
||||
|
||||
function onClick()
|
||||
noctalia.togglePanel("tadomika_ari/w-engine:w-engine-panel")
|
||||
end
|
||||
Reference in New Issue
Block a user