Claude/gnome wallpaper auto rotation r1ailq #1
@@ -13,9 +13,10 @@ schemas:
|
|||||||
build: schemas
|
build: schemas
|
||||||
|
|
||||||
install: build
|
install: build
|
||||||
mkdir -p $(INSTALL_DIR)/lib $(INSTALL_DIR)/schemas
|
mkdir -p $(INSTALL_DIR)/lib $(INSTALL_DIR)/schemas $(INSTALL_DIR)/icons
|
||||||
cp extension.js prefs.js metadata.json stylesheet.css $(INSTALL_DIR)/
|
cp extension.js prefs.js metadata.json stylesheet.css $(INSTALL_DIR)/
|
||||||
cp lib/*.js $(INSTALL_DIR)/lib/
|
cp lib/*.js $(INSTALL_DIR)/lib/
|
||||||
|
cp icons/*.svg $(INSTALL_DIR)/icons/
|
||||||
cp $(SCHEMA_DIR)/*.xml $(SCHEMA_DIR)/gschemas.compiled $(INSTALL_DIR)/schemas/
|
cp $(SCHEMA_DIR)/*.xml $(SCHEMA_DIR)/gschemas.compiled $(INSTALL_DIR)/schemas/
|
||||||
@echo "Installed to $(INSTALL_DIR)"
|
@echo "Installed to $(INSTALL_DIR)"
|
||||||
@echo "Reload GNOME Shell (Alt+F2, r, Enter on X11; log out/in on Wayland), then run:"
|
@echo "Reload GNOME Shell (Alt+F2, r, Enter on X11; log out/in on Wayland), then run:"
|
||||||
@@ -25,7 +26,7 @@ uninstall:
|
|||||||
rm -rf $(INSTALL_DIR)
|
rm -rf $(INSTALL_DIR)
|
||||||
|
|
||||||
pack: schemas
|
pack: schemas
|
||||||
gnome-extensions pack --force --extra-source=lib -o dist .
|
gnome-extensions pack --force --extra-source=lib --extra-source=icons -o dist .
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(SCHEMA_DIR)/gschemas.compiled
|
rm -f $(SCHEMA_DIR)/gschemas.compiled
|
||||||
|
|||||||
@@ -81,8 +81,8 @@ gnome-extensions prefs benthicbloom@quinta0.github.io
|
|||||||
logging, and the folders scanned for wallpapers.
|
logging, and the folders scanned for wallpapers.
|
||||||
- **Rotation** — enable/disable, interval, shuffle vs. sequential order,
|
- **Rotation** — enable/disable, interval, shuffle vs. sequential order,
|
||||||
and crossfade transition settings.
|
and crossfade transition settings.
|
||||||
- **Live Wallpaper** — enable/disable, video file, mute, playback speed,
|
- **Live Wallpaper** — enable/disable, video file, source folders to pick
|
||||||
and power-saving pause behavior.
|
a video/GIF from, mute, playback speed, and power-saving pause behavior.
|
||||||
- **OLED Protection** — master switch plus independent controls for
|
- **OLED Protection** — master switch plus independent controls for
|
||||||
pixel shifting, idle dimming, and forced periodic rotation.
|
pixel shifting, idle dimming, and forced periodic rotation.
|
||||||
|
|
||||||
@@ -99,6 +99,7 @@ lib/rotationManager.js Timer-driven wallpaper rotation + crossfade overlay
|
|||||||
lib/liveWallpaper.js GStreamer video playback rendered onto a Clutter actor
|
lib/liveWallpaper.js GStreamer video playback rendered onto a Clutter actor
|
||||||
lib/oledProtection.js Pixel shifting, idle dimming, forced rotation
|
lib/oledProtection.js Pixel shifting, idle dimming, forced rotation
|
||||||
lib/indicator.js Top-bar quick-access menu
|
lib/indicator.js Top-bar quick-access menu
|
||||||
|
icons/ Bundled symbolic icons for the preferences window
|
||||||
schemas/ GSettings schema
|
schemas/ GSettings schema
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<circle cx="8" cy="8" r="6.2" fill="none" stroke="#2e3436" stroke-width="1.3"/>
|
||||||
|
<circle cx="8" cy="4.9" r="0.95" fill="#2e3436"/>
|
||||||
|
<path d="M 8 7 L 8 11.4" fill="none" stroke="#2e3436" stroke-width="1.4" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 325 B |
@@ -0,0 +1,10 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<g fill="none" stroke="#2e3436" stroke-width="1.3" stroke-linecap="round">
|
||||||
|
<line x1="3.5" y1="2" x2="3.5" y2="14"/>
|
||||||
|
<line x1="8" y1="2" x2="8" y2="14"/>
|
||||||
|
<line x1="12.5" y1="2" x2="12.5" y2="14"/>
|
||||||
|
</g>
|
||||||
|
<circle cx="3.5" cy="5.5" r="1.7" fill="#2e3436"/>
|
||||||
|
<circle cx="8" cy="10.5" r="1.7" fill="#2e3436"/>
|
||||||
|
<circle cx="12.5" cy="7" r="1.7" fill="#2e3436"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 465 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<rect x="1.5" y="3" width="13" height="10" rx="1.8" fill="none" stroke="#2e3436" stroke-width="1.3"/>
|
||||||
|
<path d="M 6.6 5.8 L 6.6 10.2 L 10.4 8 Z" fill="#2e3436"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 256 B |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<path d="M 8 1.3 L 13.5 3.4 V 7.6 C 13.5 11.2 11.2 13.6 8 14.7 C 4.8 13.6 2.5 11.2 2.5 7.6 V 3.4 Z"
|
||||||
|
fill="none" stroke="#2e3436" stroke-width="1.3" stroke-linejoin="round"/>
|
||||||
|
<path d="M 5.6 8.1 L 7.3 9.8 L 10.6 6.2" fill="none" stroke="#2e3436" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 415 B |
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<path d="M 3.11 6.22 A 5.2 5.2 0 0 1 13.12 7.10" fill="none" stroke="#2e3436" stroke-width="1.4" stroke-linecap="round"/>
|
||||||
|
<path d="M 12.89 9.78 A 5.2 5.2 0 0 1 2.88 8.90" fill="none" stroke="#2e3436" stroke-width="1.4" stroke-linecap="round"/>
|
||||||
|
<path d="M 13.52,9.36 11.38,7.00 14.73,6.41 Z" fill="#2e3436"/>
|
||||||
|
<path d="M 2.48,6.64 4.62,9.00 1.27,9.59 Z" fill="#2e3436"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 468 B |
@@ -13,6 +13,7 @@ export const SettingsKey = Object.freeze({
|
|||||||
|
|
||||||
LIVE_WALLPAPER_ENABLED: 'live-wallpaper-enabled',
|
LIVE_WALLPAPER_ENABLED: 'live-wallpaper-enabled',
|
||||||
LIVE_WALLPAPER_PATH: 'live-wallpaper-path',
|
LIVE_WALLPAPER_PATH: 'live-wallpaper-path',
|
||||||
|
LIVE_WALLPAPER_FOLDERS: 'live-wallpaper-folders',
|
||||||
LIVE_WALLPAPER_MUTED: 'live-wallpaper-muted',
|
LIVE_WALLPAPER_MUTED: 'live-wallpaper-muted',
|
||||||
LIVE_WALLPAPER_PLAYBACK_RATE: 'live-wallpaper-playback-rate',
|
LIVE_WALLPAPER_PLAYBACK_RATE: 'live-wallpaper-playback-rate',
|
||||||
LIVE_WALLPAPER_PAUSE_ON_BATTERY: 'live-wallpaper-pause-on-battery',
|
LIVE_WALLPAPER_PAUSE_ON_BATTERY: 'live-wallpaper-pause-on-battery',
|
||||||
@@ -32,3 +33,7 @@ export const SettingsKey = Object.freeze({
|
|||||||
export const IMAGE_EXTENSIONS = Object.freeze([
|
export const IMAGE_EXTENSIONS = Object.freeze([
|
||||||
'.jpg', '.jpeg', '.png', '.webp', '.bmp', '.tiff', '.tif', '.gif',
|
'.jpg', '.jpeg', '.png', '.webp', '.bmp', '.tiff', '.tif', '.gif',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export const LIVE_WALLPAPER_EXTENSIONS = Object.freeze([
|
||||||
|
'.gif', '.mp4', '.webm', '.mkv', '.mov', '.avi', '.m4v', '.ogv',
|
||||||
|
]);
|
||||||
|
|||||||
+24
-9
@@ -1,19 +1,19 @@
|
|||||||
import Gio from 'gi://Gio';
|
import Gio from 'gi://Gio';
|
||||||
import GLib from 'gi://GLib';
|
import GLib from 'gi://GLib';
|
||||||
|
|
||||||
import {IMAGE_EXTENSIONS} from './settingsKeys.js';
|
import {IMAGE_EXTENSIONS, LIVE_WALLPAPER_EXTENSIONS} from './settingsKeys.js';
|
||||||
|
|
||||||
Gio._promisify(Gio.File.prototype, 'enumerate_children_async', 'enumerate_children_finish');
|
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, 'next_files_async', 'next_files_finish');
|
||||||
Gio._promisify(Gio.FileEnumerator.prototype, 'close_async', 'close_finish');
|
Gio._promisify(Gio.FileEnumerator.prototype, 'close_async', 'close_finish');
|
||||||
|
|
||||||
function hasImageExtension(name) {
|
function hasExtension(name, extensions) {
|
||||||
const lower = name.toLowerCase();
|
const lower = name.toLowerCase();
|
||||||
return IMAGE_EXTENSIONS.some(ext => lower.endsWith(ext));
|
return extensions.some(ext => lower.endsWith(ext));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Non-recursively lists image files directly inside a single folder. */
|
/** Non-recursively lists files with one of `extensions` directly inside a single folder. */
|
||||||
export async function listImagesInFolder(folderPath) {
|
async function listFilesInFolder(folderPath, extensions) {
|
||||||
const results = [];
|
const results = [];
|
||||||
const dir = folderPath.startsWith('file://')
|
const dir = folderPath.startsWith('file://')
|
||||||
? Gio.File.new_for_uri(folderPath)
|
? Gio.File.new_for_uri(folderPath)
|
||||||
@@ -38,7 +38,7 @@ export async function listImagesInFolder(folderPath) {
|
|||||||
for (const info of infos) {
|
for (const info of infos) {
|
||||||
if (info.get_file_type() !== Gio.FileType.REGULAR)
|
if (info.get_file_type() !== Gio.FileType.REGULAR)
|
||||||
continue;
|
continue;
|
||||||
if (!hasImageExtension(info.get_name()))
|
if (!hasExtension(info.get_name(), extensions))
|
||||||
continue;
|
continue;
|
||||||
results.push(enumerator.get_child(info).get_path());
|
results.push(enumerator.get_child(info).get_path());
|
||||||
}
|
}
|
||||||
@@ -53,10 +53,10 @@ export async function listImagesInFolder(folderPath) {
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Merges and de-duplicates images found across several folders. */
|
/** Merges and de-duplicates files with one of `extensions` found across several folders. */
|
||||||
export async function listImagesInFolders(folderPaths) {
|
async function listFilesInFolders(folderPaths, extensions) {
|
||||||
const lists = await Promise.all(
|
const lists = await Promise.all(
|
||||||
folderPaths.map(folder => listImagesInFolder(folder).catch(() => [])));
|
folderPaths.map(folder => listFilesInFolder(folder, extensions).catch(() => [])));
|
||||||
|
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
const merged = [];
|
const merged = [];
|
||||||
@@ -72,3 +72,18 @@ export async function listImagesInFolders(folderPaths) {
|
|||||||
merged.sort();
|
merged.sort();
|
||||||
return merged;
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,12 +7,18 @@ import {ExtensionPreferences, gettext as _} from 'resource:///org/gnome/Shell/Ex
|
|||||||
|
|
||||||
import {SettingsKey} from './lib/settingsKeys.js';
|
import {SettingsKey} from './lib/settingsKeys.js';
|
||||||
import {checkGstreamerBaseAvailable, GSTREAMER_INSTALL_HINT} from './lib/gstreamerAvailability.js';
|
import {checkGstreamerBaseAvailable, GSTREAMER_INSTALL_HINT} from './lib/gstreamerAvailability.js';
|
||||||
|
import {listMediaFilesInFolders} from './lib/wallpaperSource.js';
|
||||||
|
|
||||||
export default class BenthicBloomPreferences extends ExtensionPreferences {
|
export default class BenthicBloomPreferences extends ExtensionPreferences {
|
||||||
async fillPreferencesWindow(window) {
|
async fillPreferencesWindow(window) {
|
||||||
const settings = this.getSettings();
|
const settings = this.getSettings();
|
||||||
const gstreamerError = await checkGstreamerBaseAvailable().then(() => null, e => e.message ?? String(e));
|
const gstreamerError = await checkGstreamerBaseAvailable().then(() => null, e => e.message ?? String(e));
|
||||||
|
|
||||||
|
// Bundled rather than relying on the system icon theme having these
|
||||||
|
// exact names — a missing symbolic icon otherwise renders as a
|
||||||
|
// blank/broken-image placeholder in the page switcher.
|
||||||
|
Gtk.IconTheme.get_for_display(window.get_display()).add_search_path(`${this.path}/icons`);
|
||||||
|
|
||||||
window.set_default_size(640, 720);
|
window.set_default_size(640, 720);
|
||||||
window.add(this._buildGeneralPage(settings));
|
window.add(this._buildGeneralPage(settings));
|
||||||
window.add(this._buildRotationPage(settings));
|
window.add(this._buildRotationPage(settings));
|
||||||
@@ -42,7 +48,7 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
|
|||||||
// --- General page ------------------------------------------------
|
// --- General page ------------------------------------------------
|
||||||
|
|
||||||
_buildGeneralPage(settings) {
|
_buildGeneralPage(settings) {
|
||||||
const page = new Adw.PreferencesPage({title: _('General'), icon_name: 'preferences-system-symbolic'});
|
const page = new Adw.PreferencesPage({title: _('General'), icon_name: 'bb-general-symbolic'});
|
||||||
|
|
||||||
const behaviorGroup = new Adw.PreferencesGroup({title: _('Behavior')});
|
const behaviorGroup = new Adw.PreferencesGroup({title: _('Behavior')});
|
||||||
page.add(behaviorGroup);
|
page.add(behaviorGroup);
|
||||||
@@ -62,12 +68,8 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
|
|||||||
});
|
});
|
||||||
page.add(foldersGroup);
|
page.add(foldersGroup);
|
||||||
|
|
||||||
this._folderList = new Gtk.ListBox({
|
const folderList = this._buildFolderListBox(settings, SettingsKey.WALLPAPER_FOLDERS, _('No folders added yet'));
|
||||||
selection_mode: Gtk.SelectionMode.NONE,
|
foldersGroup.add(folderList);
|
||||||
css_classes: ['boxed-list'],
|
|
||||||
});
|
|
||||||
foldersGroup.add(this._folderList);
|
|
||||||
this._refreshFolderList(settings);
|
|
||||||
|
|
||||||
const addButton = new Gtk.Button({
|
const addButton = new Gtk.Button({
|
||||||
label: _('Add Folder…'),
|
label: _('Add Folder…'),
|
||||||
@@ -75,23 +77,32 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
|
|||||||
margin_top: 6,
|
margin_top: 6,
|
||||||
css_classes: ['flat'],
|
css_classes: ['flat'],
|
||||||
});
|
});
|
||||||
addButton.connect('clicked', () => this._pickFolder(settings));
|
addButton.connect('clicked', () => this._pickFolderForList(
|
||||||
|
settings, SettingsKey.WALLPAPER_FOLDERS, folderList, _('No folders added yet')));
|
||||||
foldersGroup.add(addButton);
|
foldersGroup.add(addButton);
|
||||||
|
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
_refreshFolderList(settings) {
|
// --- Shared folder-list widgets (used by the General and Live Wallpaper pages) --
|
||||||
let child = this._folderList.get_first_child();
|
|
||||||
|
_buildFolderListBox(settings, key, emptyText, onChange) {
|
||||||
|
const listBox = new Gtk.ListBox({selection_mode: Gtk.SelectionMode.NONE, css_classes: ['boxed-list']});
|
||||||
|
this._refreshFolderListBox(listBox, settings, key, emptyText, onChange);
|
||||||
|
return listBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
_refreshFolderListBox(listBox, settings, key, emptyText, onChange) {
|
||||||
|
let child = listBox.get_first_child();
|
||||||
while (child) {
|
while (child) {
|
||||||
const next = child.get_next_sibling();
|
const next = child.get_next_sibling();
|
||||||
this._folderList.remove(child);
|
listBox.remove(child);
|
||||||
child = next;
|
child = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
const folders = settings.get_strv(SettingsKey.WALLPAPER_FOLDERS);
|
const folders = settings.get_strv(key);
|
||||||
if (folders.length === 0) {
|
if (folders.length === 0) {
|
||||||
this._folderList.append(new Adw.ActionRow({title: _('No folders added yet')}));
|
listBox.append(new Adw.ActionRow({title: emptyText}));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,27 +114,29 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
|
|||||||
css_classes: ['flat'],
|
css_classes: ['flat'],
|
||||||
});
|
});
|
||||||
removeButton.connect('clicked', () => {
|
removeButton.connect('clicked', () => {
|
||||||
const current = settings.get_strv(SettingsKey.WALLPAPER_FOLDERS);
|
const current = settings.get_strv(key);
|
||||||
settings.set_strv(SettingsKey.WALLPAPER_FOLDERS, current.filter(f => f !== folder));
|
settings.set_strv(key, current.filter(f => f !== folder));
|
||||||
this._refreshFolderList(settings);
|
this._refreshFolderListBox(listBox, settings, key, emptyText, onChange);
|
||||||
|
onChange?.();
|
||||||
});
|
});
|
||||||
row.add_suffix(removeButton);
|
row.add_suffix(removeButton);
|
||||||
this._folderList.append(row);
|
listBox.append(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_pickFolder(settings) {
|
_pickFolderForList(settings, key, listBox, emptyText, onChange) {
|
||||||
const dialog = new Gtk.FileDialog({title: _('Select Wallpaper Folder')});
|
const dialog = new Gtk.FileDialog({title: _('Select Folder')});
|
||||||
dialog.select_folder(this._folderList.get_root(), null, (source, result) => {
|
dialog.select_folder(listBox.get_root(), null, (source, result) => {
|
||||||
try {
|
try {
|
||||||
const folder = dialog.select_folder_finish(result);
|
const folder = dialog.select_folder_finish(result);
|
||||||
const path = folder.get_path();
|
const path = folder.get_path();
|
||||||
if (!path)
|
if (!path)
|
||||||
return;
|
return;
|
||||||
const current = settings.get_strv(SettingsKey.WALLPAPER_FOLDERS);
|
const current = settings.get_strv(key);
|
||||||
if (!current.includes(path)) {
|
if (!current.includes(path)) {
|
||||||
settings.set_strv(SettingsKey.WALLPAPER_FOLDERS, [...current, path]);
|
settings.set_strv(key, [...current, path]);
|
||||||
this._refreshFolderList(settings);
|
this._refreshFolderListBox(listBox, settings, key, emptyText, onChange);
|
||||||
|
onChange?.();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Dialog was dismissed; nothing to do.
|
// Dialog was dismissed; nothing to do.
|
||||||
@@ -134,7 +147,7 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
|
|||||||
// --- Rotation page -------------------------------------------------
|
// --- Rotation page -------------------------------------------------
|
||||||
|
|
||||||
_buildRotationPage(settings) {
|
_buildRotationPage(settings) {
|
||||||
const page = new Adw.PreferencesPage({title: _('Rotation'), icon_name: 'media-playlist-shuffle-symbolic'});
|
const page = new Adw.PreferencesPage({title: _('Rotation'), icon_name: 'bb-rotation-symbolic'});
|
||||||
|
|
||||||
const group = new Adw.PreferencesGroup({title: _('Automatic Rotation')});
|
const group = new Adw.PreferencesGroup({title: _('Automatic Rotation')});
|
||||||
page.add(group);
|
page.add(group);
|
||||||
@@ -173,7 +186,7 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
|
|||||||
// --- Live wallpaper page --------------------------------------------
|
// --- Live wallpaper page --------------------------------------------
|
||||||
|
|
||||||
_buildLiveWallpaperPage(settings, gstreamerError) {
|
_buildLiveWallpaperPage(settings, gstreamerError) {
|
||||||
const page = new Adw.PreferencesPage({title: _('Live Wallpaper'), icon_name: 'video-x-generic-symbolic'});
|
const page = new Adw.PreferencesPage({title: _('Live Wallpaper'), icon_name: 'bb-live-wallpaper-symbolic'});
|
||||||
|
|
||||||
if (gstreamerError) {
|
if (gstreamerError) {
|
||||||
const warningGroup = new Adw.PreferencesGroup();
|
const warningGroup = new Adw.PreferencesGroup();
|
||||||
@@ -229,6 +242,7 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
|
|||||||
const path = file.get_path();
|
const path = file.get_path();
|
||||||
settings.set_string(SettingsKey.LIVE_WALLPAPER_PATH, path);
|
settings.set_string(SettingsKey.LIVE_WALLPAPER_PATH, path);
|
||||||
fileRow.subtitle = path;
|
fileRow.subtitle = path;
|
||||||
|
this._refreshLiveMediaList(settings, fileRow);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Dialog was dismissed; nothing to do.
|
// Dialog was dismissed; nothing to do.
|
||||||
}
|
}
|
||||||
@@ -251,6 +265,36 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
|
|||||||
value => settings.set_double(SettingsKey.LIVE_WALLPAPER_PLAYBACK_RATE, value));
|
value => settings.set_double(SettingsKey.LIVE_WALLPAPER_PLAYBACK_RATE, value));
|
||||||
group.add(rateRow);
|
group.add(rateRow);
|
||||||
|
|
||||||
|
const foldersGroup = new Adw.PreferencesGroup({
|
||||||
|
title: _('Wallpaper Folders'),
|
||||||
|
description: _('Videos and animated GIFs found directly inside these folders can be picked below'),
|
||||||
|
});
|
||||||
|
page.add(foldersGroup);
|
||||||
|
|
||||||
|
const mediaGroup = new Adw.PreferencesGroup({title: _('Available Media')});
|
||||||
|
page.add(mediaGroup);
|
||||||
|
this._liveMediaList = new Gtk.ListBox({selection_mode: Gtk.SelectionMode.NONE, css_classes: ['boxed-list']});
|
||||||
|
mediaGroup.add(this._liveMediaList);
|
||||||
|
this._liveMediaGeneration = 0;
|
||||||
|
|
||||||
|
const refreshMedia = () => this._refreshLiveMediaList(settings, fileRow);
|
||||||
|
|
||||||
|
const liveFolderList = this._buildFolderListBox(
|
||||||
|
settings, SettingsKey.LIVE_WALLPAPER_FOLDERS, _('No folders added yet'), refreshMedia);
|
||||||
|
foldersGroup.add(liveFolderList);
|
||||||
|
|
||||||
|
const addFolderButton = new Gtk.Button({
|
||||||
|
label: _('Add Folder…'),
|
||||||
|
halign: Gtk.Align.START,
|
||||||
|
margin_top: 6,
|
||||||
|
css_classes: ['flat'],
|
||||||
|
});
|
||||||
|
addFolderButton.connect('clicked', () => this._pickFolderForList(
|
||||||
|
settings, SettingsKey.LIVE_WALLPAPER_FOLDERS, liveFolderList, _('No folders added yet'), refreshMedia));
|
||||||
|
foldersGroup.add(addFolderButton);
|
||||||
|
|
||||||
|
refreshMedia();
|
||||||
|
|
||||||
const powerGroup = new Adw.PreferencesGroup({title: _('Power Saving')});
|
const powerGroup = new Adw.PreferencesGroup({title: _('Power Saving')});
|
||||||
page.add(powerGroup);
|
page.add(powerGroup);
|
||||||
powerGroup.add(this._switchRow(
|
powerGroup.add(this._switchRow(
|
||||||
@@ -267,16 +311,73 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
|
|||||||
fileRow.sensitive = false;
|
fileRow.sensitive = false;
|
||||||
muteRow.sensitive = false;
|
muteRow.sensitive = false;
|
||||||
rateRow.sensitive = false;
|
rateRow.sensitive = false;
|
||||||
|
foldersGroup.sensitive = false;
|
||||||
|
mediaGroup.sensitive = false;
|
||||||
powerGroup.sensitive = false;
|
powerGroup.sensitive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Re-scans the configured live wallpaper folders and repopulates the "Available Media" list. */
|
||||||
|
_refreshLiveMediaList(settings, fileRow) {
|
||||||
|
const listBox = this._liveMediaList;
|
||||||
|
const generation = ++this._liveMediaGeneration;
|
||||||
|
|
||||||
|
let child = listBox.get_first_child();
|
||||||
|
while (child) {
|
||||||
|
const next = child.get_next_sibling();
|
||||||
|
listBox.remove(child);
|
||||||
|
child = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
const folders = settings.get_strv(SettingsKey.LIVE_WALLPAPER_FOLDERS);
|
||||||
|
if (folders.length === 0) {
|
||||||
|
listBox.append(new Adw.ActionRow({title: _('Add a folder above to browse its videos and GIFs')}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
listBox.append(new Adw.ActionRow({title: _('Scanning…')}));
|
||||||
|
|
||||||
|
listMediaFilesInFolders(folders).then(paths => {
|
||||||
|
if (generation !== this._liveMediaGeneration)
|
||||||
|
return; // A folder changed again before this scan finished; a newer one is in flight.
|
||||||
|
|
||||||
|
let c = listBox.get_first_child();
|
||||||
|
while (c) {
|
||||||
|
const next = c.get_next_sibling();
|
||||||
|
listBox.remove(c);
|
||||||
|
c = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paths.length === 0) {
|
||||||
|
listBox.append(new Adw.ActionRow({title: _('No videos or GIFs found in those folders')}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentPath = settings.get_string(SettingsKey.LIVE_WALLPAPER_PATH);
|
||||||
|
for (const path of paths) {
|
||||||
|
const row = new Adw.ActionRow({
|
||||||
|
title: Gio.File.new_for_path(path).get_basename(),
|
||||||
|
subtitle: path,
|
||||||
|
activatable: true,
|
||||||
|
});
|
||||||
|
if (path === currentPath)
|
||||||
|
row.add_suffix(new Gtk.Image({icon_name: 'object-select-symbolic'}));
|
||||||
|
row.connect('activated', () => {
|
||||||
|
settings.set_string(SettingsKey.LIVE_WALLPAPER_PATH, path);
|
||||||
|
fileRow.subtitle = path;
|
||||||
|
this._refreshLiveMediaList(settings, fileRow);
|
||||||
|
});
|
||||||
|
listBox.append(row);
|
||||||
|
}
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
// --- OLED protection page --------------------------------------------
|
// --- OLED protection page --------------------------------------------
|
||||||
|
|
||||||
_buildOledPage(settings) {
|
_buildOledPage(settings) {
|
||||||
const page = new Adw.PreferencesPage({title: _('OLED Protection'), icon_name: 'weather-clear-night-symbolic'});
|
const page = new Adw.PreferencesPage({title: _('OLED Protection'), icon_name: 'bb-oled-symbolic'});
|
||||||
|
|
||||||
const group = new Adw.PreferencesGroup({
|
const group = new Adw.PreferencesGroup({
|
||||||
title: _('Burn-in Protection'),
|
title: _('Burn-in Protection'),
|
||||||
@@ -335,7 +436,7 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
|
|||||||
// --- About page -----------------------------------------------------
|
// --- About page -----------------------------------------------------
|
||||||
|
|
||||||
_buildAboutPage() {
|
_buildAboutPage() {
|
||||||
const page = new Adw.PreferencesPage({title: _('About'), icon_name: 'help-about-symbolic'});
|
const page = new Adw.PreferencesPage({title: _('About'), icon_name: 'bb-about-symbolic'});
|
||||||
const group = new Adw.PreferencesGroup();
|
const group = new Adw.PreferencesGroup();
|
||||||
page.add(group);
|
page.add(group);
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,12 @@
|
|||||||
<description>Path to the video or animated GIF file used as the live wallpaper.</description>
|
<description>Path to the video or animated GIF file used as the live wallpaper.</description>
|
||||||
</key>
|
</key>
|
||||||
|
|
||||||
|
<key name="live-wallpaper-folders" type="as">
|
||||||
|
<default>[]</default>
|
||||||
|
<summary>Live wallpaper source folders</summary>
|
||||||
|
<description>List of folder paths scanned for videos and animated GIFs to pick a live wallpaper from.</description>
|
||||||
|
</key>
|
||||||
|
|
||||||
<key name="live-wallpaper-muted" type="b">
|
<key name="live-wallpaper-muted" type="b">
|
||||||
<default>true</default>
|
<default>true</default>
|
||||||
<summary>Mute live wallpaper audio</summary>
|
<summary>Mute live wallpaper audio</summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user