Files
benthicbloom/lib/wallpaperSource.js
T
Claude f246d6a4fe Add live wallpaper source folders and bundle preferences page icons
Live Wallpaper page gains a "Wallpaper Folders" list (mirroring the
static rotation one) plus an "Available Media" list of the videos/GIFs
found inside them to pick from, backed by a new live-wallpaper-folders
GSettings key and a generalized wallpaperSource.js extension scan.

The preferences page-switcher icons (General, Rotation, Live Wallpaper,
OLED Protection, About) were showing as broken-image placeholders since
they depended on exact symbolic icon names being present in the user's
system icon theme. Bundle a small set of custom SVG icons under icons/
and register them via Gtk.IconTheme.add_search_path so they always
resolve regardless of the system theme.
2026-08-04 02:08:14 +00:00

90 lines
2.9 KiB
JavaScript

import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import {IMAGE_EXTENSIONS, LIVE_WALLPAPER_EXTENSIONS} from './settingsKeys.js';
Gio._promisify(Gio.File.prototype, 'enumerate_children_async', 'enumerate_children_finish');
Gio._promisify(Gio.FileEnumerator.prototype, 'next_files_async', 'next_files_finish');
Gio._promisify(Gio.FileEnumerator.prototype, 'close_async', 'close_finish');
function hasExtension(name, extensions) {
const lower = name.toLowerCase();
return extensions.some(ext => lower.endsWith(ext));
}
/** Non-recursively lists files with one of `extensions` directly inside a single folder. */
async function listFilesInFolder(folderPath, extensions) {
const results = [];
const dir = folderPath.startsWith('file://')
? Gio.File.new_for_uri(folderPath)
: Gio.File.new_for_path(folderPath);
let enumerator;
try {
enumerator = await dir.enumerate_children_async(
'standard::name,standard::type',
Gio.FileQueryInfoFlags.NONE,
GLib.PRIORITY_DEFAULT,
null);
} catch (e) {
return results;
}
for (;;) {
const infos = await enumerator.next_files_async(50, GLib.PRIORITY_DEFAULT, null);
if (infos.length === 0)
break;
for (const info of infos) {
if (info.get_file_type() !== Gio.FileType.REGULAR)
continue;
if (!hasExtension(info.get_name(), extensions))
continue;
results.push(enumerator.get_child(info).get_path());
}
}
try {
await enumerator.close_async(GLib.PRIORITY_DEFAULT, null);
} catch (e) {
// Enumerator already exhausted; nothing to clean up.
}
return results;
}
/** Merges and de-duplicates files with one of `extensions` found across several folders. */
async function listFilesInFolders(folderPaths, extensions) {
const lists = await Promise.all(
folderPaths.map(folder => listFilesInFolder(folder, extensions).catch(() => [])));
const seen = new Set();
const merged = [];
for (const list of lists) {
for (const path of list) {
if (!seen.has(path)) {
seen.add(path);
merged.push(path);
}
}
}
merged.sort();
return merged;
}
/** Non-recursively lists image files directly inside a single folder. */
export async function listImagesInFolder(folderPath) {
return listFilesInFolder(folderPath, IMAGE_EXTENSIONS);
}
/** Merges and de-duplicates images found across several folders. */
export async function listImagesInFolders(folderPaths) {
return listFilesInFolders(folderPaths, IMAGE_EXTENSIONS);
}
/** Merges and de-duplicates live-wallpaper-capable videos/GIFs found across several folders. */
export async function listMediaFilesInFolders(folderPaths) {
return listFilesInFolders(folderPaths, LIVE_WALLPAPER_EXTENSIONS);
}