diff --git a/desktop-launcher/README.md b/desktop-launcher/README.md new file mode 100644 index 00000000..bd836b90 --- /dev/null +++ b/desktop-launcher/README.md @@ -0,0 +1,26 @@ +# Desktop Launcher + +Desktop widget to launch your favorite apps from your desktop in one click. + +## Plugin + +| Field | Value | +| --- | --- | +| ID | `yocraft/desktop-launcher` | +| Entries | Desktop widget: `widget` | + +## Usage + +Add it to your desktop/lockscreen widgets using the widget editor. You can configure it in the widget settings. + +## Settings + +| Setting | Type | Default | Description | +| --- | --- | --- | --- | +| `apps` | `string_list` | `` | Apps list and order. | +| `layout` | `bool` | `false` | Display applications in a grid layout. | +| `columns` | `int` | `5` | Number of columns for the grid layout. | + +## Notes + +Flatpak is supported. diff --git a/desktop-launcher/plugin.toml b/desktop-launcher/plugin.toml new file mode 100644 index 00000000..01dd1628 --- /dev/null +++ b/desktop-launcher/plugin.toml @@ -0,0 +1,40 @@ +id = "yocraft/desktop-launcher" +name = "Desktop Launcher" +version = "1.0.0" +plugin_api = 3 +author = "yocraft" +license = "MIT" +deprecated = false +icon = "launcher" +description = "Launch your favorite apps from your desktop in one click." +tags = [ "desktop", "utility" ] +dependencies = [] + +[[desktop_widget]] +id = "widget" +entry = "widget.luau" + + [[desktop_widget.setting]] + key = "apps" + type = "string_list" + label_key = "settings.apps.label" + default = [ + "firefox", + "foot", + "nvim", + ] + + [[desktop_widget.setting]] + key = "grid_layout" + type = "bool" + label_key = "settings.grid_layout.label" + default = false + + [[desktop_widget.setting]] + key = "columns" + type = "int" + label_key = "settings.columns.label" + default = 5 + min = 1 + max = 20 + visible_when = { key = "grid_layout", values = ["true"] } diff --git a/desktop-launcher/thumbnail.webp b/desktop-launcher/thumbnail.webp new file mode 100644 index 00000000..d2067562 Binary files /dev/null and b/desktop-launcher/thumbnail.webp differ diff --git a/desktop-launcher/translations/en.json b/desktop-launcher/translations/en.json new file mode 100644 index 00000000..dfacb6ce --- /dev/null +++ b/desktop-launcher/translations/en.json @@ -0,0 +1,13 @@ +{ + "settings": { + "apps": { + "label": "Applications" + }, + "grid_layout": { + "label": "Grid Layout" + }, + "columns": { + "label": "Columns" + } + } +} diff --git a/desktop-launcher/widget.luau b/desktop-launcher/widget.luau new file mode 100644 index 00000000..f03488e4 --- /dev/null +++ b/desktop-launcher/widget.luau @@ -0,0 +1,168 @@ +local function getConf(key, default) + local value = noctalia.getConfig(key) + if value == nil then + return default + else + return value + end +end + +local config = { + grid_layout = getConf("grid_layout", false), + columns = getConf("columns", 5), +} + +-- https://github.com/noctalia-dev/noctalia/blob/main/src/system/desktop_entry_launch.cpp#L45 +local function stripFieldCodes(exec) + local result = exec:gsub("%%(.?)( ?)", function(nextChar, trailingSpace) + if nextChar:match("[fFuUdDnNick]") then + return "" + elseif nextChar == "%" then + return "%" .. trailingSpace + else + return "%" .. nextChar .. trailingSpace + end + end) + + result = result:gsub(" +$", "") + + -- Flatpak + result = result:gsub("@@u *@@", "") + + return result +end + +local function getContent(appid) + local paths = { + "~/.local/share/applications/", + "~/.local/share/flatpak/exports/share/applications/", + "/usr/local/share/applications/", + "/usr/share/applications/", + "/var/lib/flatpak/exports/share/applications/", + } + + for _, path in ipairs(paths) do + local content = noctalia.readFile(path .. appid .. ".desktop") + if content then + return content + end + end + + return nil +end + +local function getKeyContent(content, key) + return content:match("[\r\n]" .. key .. "=([^\r\n]*)") +end + +local function parse(appid) + local content = getContent(appid) + if not content then + return nil + end + + local data = {} + + local name = getKeyContent(content, "Name") + if name then + data.name = name + end + + local exec = getKeyContent(content, "Exec") + if exec then + data.exec = stripFieldCodes(exec) + end + + local terminal = getKeyContent(content, "Terminal") + if terminal then + data.terminal = terminal == "true" + end + + return data +end + +local function appBox(app, data) + return ui.column({ + fill = "surface", + padding = 8, + radius = 12, + align = "center", + minWidth = 100, + justify = "space_between", + gap = 4, + onClick = function() launchApp(data.exec, data.terminal) end + }, { + ui.image({ + path = noctalia.appIconPath(app), + width = 60, + height = 60, + }), + ui.label({ + text = data.name, + fontWeight = "heavy", + maxWidth = 84, + maxLines = 1, + fontSize = 14, + }) + }) +end + +local function getApps() + local apps = noctalia.getConfig("apps") + local appsTable = {} + + for _, app in ipairs(apps) do + local data = parse(app) + if not data then + noctalia.log("desktop-launcher: invalid desktop entry, skipping " .. app) + continue + end + + table.insert(appsTable, appBox(app, data)) + end + + return appsTable +end + +local function getRows(apps) + local rows = {} + local row = {} + + for i, app in ipairs(apps) do + table.insert(row, app) + + if #row == config.columns or i == #apps then + table.insert(rows, + ui.row({ gap = 8 }, row) + ) + + row = {} + end + end + + return rows +end + +local function render() + local apps = getApps() + + if config.grid_layout then + desktopWidget.render(ui.column({ gap = 8, }, + getRows(apps) + )) + else + desktopWidget.render(ui.row({ gap = 8, }, + apps + )) + end +end + +function launchApp(exec, terminal) + if terminal then + noctalia.runInTerminal(exec) + else + noctalia.runAsync(exec) + end +end + +render()