From e7b0861f69d3e1b37b428c9146b5410853e44531 Mon Sep 17 00:00:00 2001 From: Jack Stehn Date: Wed, 19 Aug 2026 03:15:55 -0700 Subject: [PATCH] daily-wallpaper: add bar widget, panel, and desktop widget for the caption MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bing and NASA both ship a caption alongside their photo of the day, but the service only used it to build the wallpaper's cache filename, discarding the text itself. Captures it at the point each source resolves and publishes it as plugin state, then adds three views: a bar widget (glyph + optional short caption; click opens the panel), a panel with the full caption, credit, and a "learn more" link, and a matching desktop widget card. - Bing's `copyright` field is split into a title and a "(© ...)" credit. - NASA's gallery-item caption has no separate credit line; its own article link is used as "learn more" instead of the Bing quiz link. - Metadata is fetched even when the day's image is already cached (e.g. after a process restart), independent of the download step. - NASA extraction (image, caption, link) is bounded to a small substring around the first gallery item rather than scanning the whole page: the original whole-page regex intermittently exceeded Noctalia's per-callback CPU budget against the real nasa.gov page. --- daily-wallpaper/README.md | 34 ++++++++++-- daily-wallpaper/daily_wallpaper.luau | 80 +++++++++++++++++++++++++--- daily-wallpaper/desktop_widget.luau | 37 +++++++++++++ daily-wallpaper/panel.luau | 75 ++++++++++++++++++++++++++ daily-wallpaper/plugin.toml | 44 +++++++++++++-- daily-wallpaper/translations/en.json | 25 +++++++++ daily-wallpaper/widget.luau | 52 ++++++++++++++++++ 7 files changed, 333 insertions(+), 14 deletions(-) create mode 100644 daily-wallpaper/desktop_widget.luau create mode 100644 daily-wallpaper/panel.luau create mode 100644 daily-wallpaper/widget.luau diff --git a/daily-wallpaper/README.md b/daily-wallpaper/README.md index d8097eda..53060306 100644 --- a/daily-wallpaper/README.md +++ b/daily-wallpaper/README.md @@ -1,14 +1,19 @@ # Daily Wallpaper -Daily Wallpaper fetches Bing's image of the day or NASA's image of the day and -applies it through Noctalia's wallpaper API. +Daily Wallpaper fetches Bing's image of the day or NASA's image of the day, +applies it through Noctalia's wallpaper API, and shows the caption/credit that +came with it — on the bar, in a panel, and on the desktop. ## Plugin | Field | Value | | --- | --- | | ID | `nzlov/daily-wallpaper` | -| Entry | Service: `service` | +| Entries | Service: `service`; bar widget: `widget`; panel: `panel`; desktop widget: `desktop` | + +## Requirements + +Install `xdg-utils` on `PATH` (for the panel's "Learn more" button, via `xdg-open`). ## Usage @@ -19,12 +24,27 @@ per source and Bing locale each day. Choose Bing or NASA under the plugin's settings. Bing accepts a market locale such as `en-US`, `de-DE`, or `fr-FR`; NASA ignores the locale setting. +Add the `widget` bar entry and/or the `desktop` desktop widget from Settings +to see today's caption. Clicking the bar glyph (or running +`noctalia msg panel-toggle nzlov/daily-wallpaper:panel`) opens the full panel +with the caption, credit (Bing only), and a "Learn more" link — Bing's quiz +page, or NASA's full article about the image. + ## Settings | Setting | Type | Default | Description | | --- | --- | --- | --- | | `source` | `select` | `bing` | Selects Bing or NASA as the daily image source. | | `locale` | `string` | *(automatic)* | Bing market locale; an empty value uses the service default. | +| `widget.glyph` | `glyph` | `photo` | Bar icon. | +| `widget.show_text` | `bool` | `true` | Show a truncated caption next to the bar glyph. | +| `widget.max_chars` | `int` | `28` | Max caption length shown on the bar before truncating with an ellipsis. | + +## IPC + +```sh +noctalia msg plugin nzlov/daily-wallpaper:service all refresh +``` ## Notes @@ -32,3 +52,11 @@ The service contacts the selected provider and downloads images into a dedicated `daily-wallpaper` cache directory. It removes cached images older than five days. Repeated failures are logged, but error notifications are limited to once per day. + +Neither source gives a wallpaper-application step any reason to keep the +caption text that comes with the image, so it is captured separately at the +point each source resolves and published as plugin state for the bar widget, +panel, and desktop widget to display. Bing's `copyright` field is one caption +line, e.g. "Runners at the base of Victoria Falls, Zimbabwe (© Byron.../Getty +Images)", which is split into a title and a credit; NASA's gallery-item +caption is a single sentence with no separate credit line. diff --git a/daily-wallpaper/daily_wallpaper.luau b/daily-wallpaper/daily_wallpaper.luau index dce80ede..c0a3f411 100644 --- a/daily-wallpaper/daily_wallpaper.luau +++ b/daily-wallpaper/daily_wallpaper.luau @@ -1,7 +1,8 @@ --!nonstrict -- Daily Wallpaper service for Noctalia 5. -- Fetches one Bing or NASA image per day, caches it locally, and applies it via --- the native Noctalia wallpaper API. +-- the native Noctalia wallpaper API. Also captures each source's caption and +-- publishes it as "today" state for widget.luau/panel.luau/desktop_widget.luau. local CHECK_INTERVAL_MS = 10 * 60 * 1000 local RETENTION_SECONDS = 5 * 24 * 60 * 60 @@ -49,10 +50,40 @@ end local function decodeHtml(value) local decoded = value:gsub("&", "&") decoded = decoded:gsub(""", '"') - decoded = decoded:gsub("'", "'") + -- Numeric entities, e.g. ' or '. + decoded = decoded:gsub("&#(%d+);", function(n) + local code = tonumber(n) + return (code and code < 256) and string.char(code) or "" + end) return decoded end +-- Bing's `copyright` field is "Caption text (© Photographer/Source)"; split +-- off the trailing credit so title and credit can be shown separately. +local function splitCopyright(copyright) + local body, credit = copyright:match("^(.-)%s*%(([^()]*)%)%s*$") + if body and credit then + return decodeHtml(body), decodeHtml(credit) + end + return decodeHtml(copyright), "" +end + +-- Publishes today's caption/credit/link for the bar widget, panel, and +-- desktop widget to read via noctalia.state. +local function publishToday(selectedSource, date, info) + if type(info) ~= "table" then + return + end + noctalia.state.set("today", { + date = date, + source = selectedSource, + title = info.title or "", + credit = info.credit or "", + link = info.link or "", + }) + noctalia.log(`Daily Wallpaper caption: {info.title or ""}`) +end + local function absoluteNasaUrl(url) if url == nil or url == "" then return "" @@ -170,7 +201,12 @@ local function resolveBing(locale, done) return end - done(`bing-{locale}`, `https://www.bing.com{urlBase}_UHD.jpg`, `https://www.bing.com{urlBase}_1920x1080.jpg`) + local title, credit = splitCopyright(first.copyright or "") + local link = (type(first.copyrightlink) == "string" and first.copyrightlink ~= "") + and first.copyrightlink or "" + + done(`bing-{locale}`, `https://www.bing.com{urlBase}_UHD.jpg`, `https://www.bing.com{urlBase}_1920x1080.jpg`, + { title = title, credit = credit, link = link }) end) if not started then @@ -178,6 +214,16 @@ local function resolveBing(locale, done) end end +-- A small window of markup around the first gallery item, so later matches +-- run against a bounded string instead of the whole page. +local function firstNasaGalleryItem(page) + local s = page:find("hds-gallery-item-single hds-gallery-image", 1, true) + if not s then + return nil + end + return page:sub(s, s + 4000) +end + local function resolveNasa(done) local started = noctalia.http({ url = "https://www.nasa.gov/image-of-the-day/" }, function(res) if not res.ok or res.status < 200 or res.status >= 300 then @@ -186,8 +232,17 @@ local function resolveNasa(done) end local page = res.body - local primary = page:match('class="[^"]-hds%-gallery%-image[^"]-"[^>]->.-]-src="([^"]+)"') - or page:match(']-class="[^"]-hds%-gallery%-image[^"]-"[^>]-src="([^"]+)"') + local item = firstNasaGalleryItem(page) + + local primary, caption, link + if item then + primary = item:match(']-src="([^"]+)"') + -- NASA has no separate credit line (unlike Bing), so the whole + -- caption becomes the title and credit stays empty. + caption = item:match('class="hds%-gallery%-item%-caption[^"]*">([^<]*)') + link = item:match('class="hds%-gallery%-item%-link[^"]*"%s+href="([^"]+)"') + end + local fallback = page:match('/dev/null 2>&1") + end +end + +noctalia.state.watch("today", render) +noctalia.state.watch("status", render) diff --git a/daily-wallpaper/plugin.toml b/daily-wallpaper/plugin.toml index 56ddde89..4bbecf3e 100644 --- a/daily-wallpaper/plugin.toml +++ b/daily-wallpaper/plugin.toml @@ -1,13 +1,13 @@ id = "nzlov/daily-wallpaper" name = "Daily Wallpaper" -version = "1.0.0" +version = "1.1.0" plugin_api = 3 author = "nzlov" license = "MIT" -dependencies = [] -tags = ["wallpaper", "desktop"] +dependencies = ["xdg-open"] +tags = ["wallpaper", "desktop", "bar", "panel", "service"] icon = "photo" -description = "Fetches a new daily wallpaper from Bing or NASA." +description = "Fetches a new daily wallpaper from Bing or NASA, and shows its caption on the bar, in a panel, and on the desktop." [[setting]] key = "source" @@ -29,3 +29,39 @@ default = "" [[service]] id = "service" entry = "daily_wallpaper.luau" + +[[widget]] +id = "widget" +entry = "widget.luau" + + [[widget.setting]] + key = "glyph" + type = "glyph" + label_key = "settings.widget.glyph.label" + default = "photo" + + [[widget.setting]] + key = "show_text" + type = "bool" + label_key = "settings.widget.show_text.label" + default = true + + [[widget.setting]] + key = "max_chars" + type = "int" + label_key = "settings.widget.max_chars.label" + default = 28 + min = 8 + max = 80 + +[[panel]] +id = "panel" +entry = "panel.luau" +width = 420 +height = 250 +placement = "attached" +open_near_click = true + +[[desktop_widget]] +id = "desktop" +entry = "desktop_widget.luau" diff --git a/daily-wallpaper/translations/en.json b/daily-wallpaper/translations/en.json index b6e215f2..92ea385e 100644 --- a/daily-wallpaper/translations/en.json +++ b/daily-wallpaper/translations/en.json @@ -14,6 +14,31 @@ "bing": "Bing", "nasa": "NASA" } + }, + "widget": { + "glyph": { + "label": "Glyph" + }, + "show_text": { + "label": "Show caption text next to glyph" + }, + "max_chars": { + "label": "Max caption characters on the bar" + } } + }, + "widget": { + "title": "Daily Wallpaper", + "loading_short": "Loading…", + "tooltip_loading": "Fetching today's wallpaper caption…", + "tooltip_error": "Could not fetch today's wallpaper caption.", + "panel_launch_failed": "Could not open the Daily Wallpaper panel." + }, + "panel": { + "title": "Daily Wallpaper", + "loading": "Fetching today's wallpaper caption…", + "error": "Could not fetch today's wallpaper caption.", + "date": "{source} · {date}", + "learn_more": "Learn more" } } diff --git a/daily-wallpaper/widget.luau b/daily-wallpaper/widget.luau new file mode 100644 index 00000000..c236dc02 --- /dev/null +++ b/daily-wallpaper/widget.luau @@ -0,0 +1,52 @@ +--!nonstrict +-- Bar widget: a glyph (+ optional short caption text) reflecting today's +-- wallpaper. Pure view over daily_wallpaper.luau's "today"/"status" state. +-- Click opens the panel with the full caption, credit, and "learn more" link. + +local function tr(key, args) return noctalia.tr(key, args) end + +local glyph = noctalia.getConfig("glyph") or "photo" +local showText = noctalia.getConfig("show_text") +local maxChars = tonumber(noctalia.getConfig("max_chars")) or 28 + +local today = noctalia.state.get("today") +local status = noctalia.state.get("status") + +local function truncate(text) + if #text <= maxChars then + return text + end + return text:sub(1, maxChars - 1) .. "…" +end + +local function render() + barWidget.setGlyph(glyph) + + if type(today) == "table" and type(today.title) == "string" and today.title ~= "" then + barWidget.setText(showText and truncate(today.title) or "") + barWidget.setTooltip(today.title) + else + barWidget.setText(showText and tr("widget.loading_short") or "") + local msg = (type(status) == "table" and status.state == "error") + and (status.message or tr("widget.tooltip_error")) + or tr("widget.tooltip_loading") + barWidget.setTooltip(msg) + end +end + +noctalia.state.watch("today", function(value) + today = value + render() +end) +noctalia.state.watch("status", function(value) + status = value + render() +end) + +function onClick() + if not noctalia.runAsync("noctalia msg panel-toggle 'nzlov/daily-wallpaper:panel'") then + noctalia.notifyError(tr("widget.title"), tr("widget.panel_launch_failed")) + end +end + +render()