feat: add prismlauncher instances plugin (#15)

* feat: add prismlauncher instances plugin

* declare prismlauncher dependency

* Remove leading slash from launcher prefix

* Change tags for plugin.toml

Updated tags in plugin.toml to better reflect functionality.

* fix: use noctalia.listDir

* mention prismlauncher dependency in the readme

---------

Co-authored-by: Lemmy <studio@quadbyte.net>
This commit is contained in:
radimous
2026-07-15 09:30:44 -04:00
committed by GitHub
co-authored by Lemmy
parent 1e69688bc2
commit 5ccb52baae
5 changed files with 221 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
# PrismLauncher Instances
![prismlauncher instances thumbnail](thumbnail.webp)
**PrismLauncher Instances** is a plugin that adds PrismLauncher instances to the noctalia launcher.
## Requirements
- [prismlauncher](https://github.com/PrismLauncher/PrismLauncher)
+26
View File
@@ -0,0 +1,26 @@
id = "radimous/prismlauncher-instances"
name = "PrismLauncher Instances"
version = "1.0.0"
min_noctalia = "5.0.0"
author = "radimous"
license = "MIT"
dependencies = ["prismlauncher"]
icon = "nut"
deprecated = false
description = "A launcher provider that adds PrismLauncher instances to the noctalia launcher."
tags = ["gaming", "launcher"]
[[launcher_provider]]
id = "prismlauncher-instances"
entry = "prismlauncher-instances.luau"
prefix = "pl"
glyph = "nut"
include_in_global_search = false
debounce_ms = 0
[[setting]]
key = "prism_path"
type = "string"
label_key = "settings.prismlauncher_path.label"
description_key = "settings.prismlauncher_path.description"
default = "~/.local/share/PrismLauncher"
@@ -0,0 +1,177 @@
--!nonstrict
local function getPrismPath()
local prismPath = noctalia.getConfig("prism_path")
return noctalia.expandPath(prismPath)
end
local function getInstancesDir()
local prismPath = getPrismPath()
local configuredInstanceDir = getCfgValue(prismPath .. "/prismlauncher.cfg", "InstanceDir")
local instances
if configuredInstanceDir == nil then -- default path
instances = prismPath .. "/instances"
elseif configuredInstanceDir:sub(1, 1) == "/" then -- absolute path
instances = configuredInstanceDir
else -- relative path
instances = prismPath .. "/" .. configuredInstanceDir
end
return instances
end
local function loadInstances()
local instances = {}
local list = noctalia.listDir(getInstancesDir())
if list ~= nil then
for _, name in ipairs(list) do
local instanceDir = getInstancesDir() .. "/" .. name
if name ~= "" and noctalia.fileExists(instanceDir .. "/instance.cfg") then -- same check as prism to only show instance dirs
table.insert(instances, name)
end
end
end
return instances
end
local function instanceName(path)
local instanceCfg = getInstancesDir() .. "/" .. path .. "/instance.cfg"
local configuredName = getCfgValue(instanceCfg, "name")
if configuredName ~= nil then
return configuredName
end
return path:match("([^/]+)$") or path
end
local function filterInstances(instances, query)
if query == "" then return instances end
local results = {}
for _, path in instances do
local insName = instanceName(path)
if insName:lower():find(query:lower(), 1, true) then
table.insert(results, path)
end
end
return results
end
local function makeRows(instances)
local rows = {}
for _, path in instances do
table.insert(rows, {
id = path,
title = instanceName(path),
subtitle = path,
icon = getIcon(path),
glyph = "nut",
})
end
return rows
end
local function showResults(query, instances, filter)
local filtered = filterInstances(instances, filter)
if #filtered == 0 then
launcher.setResults(query, {
{ id = "", title = noctalia.tr("no_instances.title"), subtitle = filter ~= "" and noctalia.tr("no_instances.subtitle") .. filter, glyph = "folder-x" }
})
else
launcher.setResults(query, makeRows(filtered))
end
end
function onQuery(query)
local filter = noctalia.string.trim(query)
showResults(query, loadInstances(), filter)
end
local function shellQuote(s)
return "'" .. s:gsub("'", "'\"'\"'") .. "'"
end
function onActivate(id)
if id == "" then return end
noctalia.runAsync(string.format("prismlauncher --launch %s &>/dev/null", shellQuote(id)))
end
-- doesn't handle icons that were changed to different default icon, rest is fine
-- never change icons since creating instance => OK
-- change icon to manually provided icon or to icon from mod provider => OK
-- change icon to different default icon baked into prism binary => NOK, old icon will be shown because <instance>/minecraft/icon.png isn't updated
function getIcon(instancePath)
local instanceFullPath = getInstancesDir() .. "/" .. instancePath
local defaultIcon = instanceFullPath .. "/minecraft/icon.png"
local cfgPath = instanceFullPath .. "/instance.cfg"
local iconVal = getCfgValue(cfgPath, "iconKey")
if (iconVal == nil) then
return defaultIcon
end
local iconDir = getPrismPath() .. "/icons/"
local bestIcon = findBestIconIn(iconDir, iconVal)
if (bestIcon == nil) then
return defaultIcon
end
return bestIcon
end
-- https://github.com/PrismLauncher/PrismLauncher/blob/d2fa7cf7f7aa8fa5e3d4f5f7b474621c732cd525/launcher/icons/IconUtils.cpp
local validIconExtensions = { "svg", "png", "ico", "gif", "jpg", "jpeg", "webp" }
local function isIconSuffix(suffix)
for _, ext in ipairs(validIconExtensions) do
if ext == suffix then
return true
end
end
return false
end
local function splitNameAndSuffix(filename)
local dotIndex = filename:find("%.[^.]*$")
if not dotIndex then
return filename, ""
end
local base = filename:sub(1, dotIndex - 1)
local suffix = filename:sub(dotIndex + 1)
return base, suffix
end
function findBestIconIn(folder, iconKey)
local entries = noctalia.listDir(folder)
if not entries then
return nil
end
for _, filename in ipairs(entries) do
local baseName, suffix = splitNameAndSuffix(filename)
if (baseName == iconKey or filename == iconKey) and isIconSuffix(suffix) then
local sep = folder:sub(-1) == "/" and "" or "/"
return folder .. sep .. filename
end
end
return nil
end
function getCfgValue(cfgPath, key) -- naive INI value fetcher, doesn't handle sections, it's enough for this usecase
local content, err = noctalia.readFile(cfgPath)
if not content then
noctalia.log("Failed to read file ".. cfgPath .. " : " .. tostring(err))
return nil
end
for line in content:gmatch("[^\r\n]+") do
if line:sub(1, #key + 1) == key .. "=" then
return line:sub(#key + 2)
end
end
return nil
end
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

@@ -0,0 +1,12 @@
{
"settings": {
"prismlauncher_path": {
"label": "PrismLauncher path",
"description": "Path to PrismLauncher"
}
},
"no_instances": {
"title": "No instances found",
"subtitle": "Filter: "
}
}