[gnome-shell-extensions] user-themes: Add preference widget



commit 739b1e11a0ee38eccf244a68431baa7a041acd18
Author: Florian Müllner <fmuellner gnome org>
Date:   Sat Apr 11 05:29:56 2020 +0200

    user-themes: Add preference widget
    
    While we don't endorse or support 3rd party theming, the extension
    exists and is actively used. However right now the most convenient
    way of setting it up is by installing Tweak Tool; give users an
    alternative by providing a simple settings dialog ourselves.
    
    https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/117

 extensions/user-theme/extension.js |  14 ++-
 extensions/user-theme/meson.build  |   1 +
 extensions/user-theme/prefs.js     | 176 +++++++++++++++++++++++++++++++++++++
 extensions/user-theme/util.js      |  12 +++
 4 files changed, 195 insertions(+), 8 deletions(-)
---
diff --git a/extensions/user-theme/extension.js b/extensions/user-theme/extension.js
index 04da83b..dff00f8 100644
--- a/extensions/user-theme/extension.js
+++ b/extensions/user-theme/extension.js
@@ -2,11 +2,14 @@
 // Load shell theme from ~/.local/share/themes/name/gnome-shell
 /* exported init */
 
-const { Gio, GLib } = imports.gi;
+const { Gio } = imports.gi;
 
 const ExtensionUtils = imports.misc.extensionUtils;
 const Main = imports.ui.main;
 
+const Me = ExtensionUtils.getCurrentExtension();
+const Util = Me.imports.util;
+
 const SETTINGS_KEY = 'name';
 
 class ThemeManager {
@@ -34,13 +37,8 @@ class ThemeManager {
         let themeName = this._settings.get_string(SETTINGS_KEY);
 
         if (themeName) {
-            let stylesheetPaths = [
-                [GLib.get_home_dir(), '.themes'],
-                [GLib.get_user_data_dir(), 'themes'],
-                ...GLib.get_system_data_dirs().map(dir => [dir, 'themes']),
-            ].map(themeDir => GLib.build_filenamev([
-                ...themeDir, themeName, 'gnome-shell', 'gnome-shell.css',
-            ]));
+            const stylesheetPaths = Util.getThemeDirs()
+                .map(dir => `${dir}/${themeName}/gnome-shell/gnome-shell.css`);
 
             stylesheet = stylesheetPaths.find(path => {
                 let file = Gio.file_new_for_path(path);
diff --git a/extensions/user-theme/meson.build b/extensions/user-theme/meson.build
index 585c02d..e7c66d4 100644
--- a/extensions/user-theme/meson.build
+++ b/extensions/user-theme/meson.build
@@ -4,4 +4,5 @@ extension_data += configure_file(
   configuration: metadata_conf
 )
 
+extension_sources += files('prefs.js', 'util.js')
 extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml')
diff --git a/extensions/user-theme/prefs.js b/extensions/user-theme/prefs.js
new file mode 100644
index 0000000..2f52af5
--- /dev/null
+++ b/extensions/user-theme/prefs.js
@@ -0,0 +1,176 @@
+// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
+/* exported init buildPrefsWidget */
+
+// we use async/await here to not block the mainloop, not to parallelize
+/* eslint-disable no-await-in-loop */
+
+const { Gio, GLib, GObject, Gtk } = imports.gi;
+
+const ExtensionUtils = imports.misc.extensionUtils;
+
+const Me = ExtensionUtils.getCurrentExtension();
+const Util = Me.imports.util;
+
+Gio._promisify(Gio._LocalFilePrototype,
+    'enumerate_children_async', 'enumerate_children_finish');
+Gio._promisify(Gio._LocalFilePrototype,
+    'query_info_async', 'query_info_finish');
+Gio._promisify(Gio.FileEnumerator.prototype,
+    'next_files_async', 'next_files_finish');
+
+const UserThemePrefsWidget = GObject.registerClass(
+class UserThemePrefsWidget extends Gtk.ScrolledWindow {
+    _init() {
+        super._init({
+            hscrollbar_policy: Gtk.PolicyType.NEVER,
+        });
+
+        const box = new Gtk.Box();
+        this.add(box);
+
+        this._list = new Gtk.ListBox({
+            halign: Gtk.Align.CENTER,
+            valign: Gtk.Align.START,
+            hexpand: true,
+            margin: 60,
+        });
+        this._list.get_style_context().add_class('frame');
+        this._list.set_header_func(this._updateHeader.bind(this));
+        box.add(this._list);
+
+        this._actionGroup = new Gio.SimpleActionGroup();
+        this._list.insert_action_group('theme', this._actionGroup);
+
+        this._settings = ExtensionUtils.getSettings();
+        this._actionGroup.add_action(
+            this._settings.create_action('name'));
+
+        this.connect('destroy', () => this._settings.run_dispose());
+
+        this._rows = new Map();
+        this._addTheme(''); // default
+
+        this._collectThemes();
+    }
+
+    async _collectThemes() {
+        for (const dirName of Util.getThemeDirs()) {
+            const dir = Gio.File.new_for_path(dirName);
+            for (const name of await this._enumerateDir(dir)) {
+                if (this._rows.has(name))
+                    continue;
+
+                const file = dir.resolve_relative_path(
+                    `${name}/gnome-shell/gnome-shell.css`);
+                try {
+                    await file.query_info_async(
+                        Gio.FILE_ATTRIBUTE_STANDARD_NAME,
+                        Gio.FileQueryInfoFlags.NONE,
+                        GLib.PRIORITY_DEFAULT, null);
+                    this._addTheme(name);
+                } catch (e) {
+                    if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
+                        logError(e);
+                }
+            }
+        }
+    }
+
+    _addTheme(name) {
+        const row = new ThemeRow(name);
+        this._rows.set(name, row);
+
+        this._list.add(row);
+        row.show_all();
+    }
+
+    async _enumerateDir(dir) {
+        const fileInfos = [];
+        let fileEnum;
+
+        try {
+            fileEnum = await dir.enumerate_children_async(
+                Gio.FILE_ATTRIBUTE_STANDARD_NAME,
+                Gio.FileQueryInfoFlags.NONE,
+                GLib.PRIORITY_DEFAULT, null);
+        } catch (e) {
+            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
+                logError(e);
+            return [];
+        }
+
+        let infos;
+        do {
+            infos = await fileEnum.next_files_async(100,
+                GLib.PRIORITY_DEFAULT, null);
+            fileInfos.push(...infos);
+        } while (infos.length > 0);
+
+        return fileInfos.map(info => info.get_name());
+    }
+
+    _updateHeader(row, before) {
+        if (!before || row.get_header())
+            return;
+        row.set_header(new Gtk.Separator());
+    }
+});
+
+const ThemeRow = GObject.registerClass(
+class ThemeRow extends Gtk.ListBoxRow {
+    _init(name) {
+        this._name = new GLib.Variant('s', name);
+
+        super._init({
+            action_name: 'theme.name',
+            action_target: this._name,
+        });
+
+        const box = new Gtk.Box({
+            spacing: 12,
+            margin: 12,
+        });
+        this.add(box);
+
+        box.add(new Gtk.Label({
+            label: name || 'Default',
+            hexpand: true,
+            xalign: 0,
+            max_width_chars: 25,
+            width_chars: 25,
+        }));
+
+        this._checkmark = new Gtk.Image({
+            icon_name: 'emblem-ok-symbolic',
+            pixel_size: 16,
+        });
+        box.add(this._checkmark);
+
+        box.show_all();
+
+        const id = this.connect('parent-set', () => {
+            this.disconnect(id);
+
+            const actionGroup = this.get_action_group('theme');
+            actionGroup.connect('action-state-changed::name',
+                this._syncCheckmark.bind(this));
+            this._syncCheckmark();
+        });
+    }
+
+    _syncCheckmark() {
+        const actionGroup = this.get_action_group('theme');
+        const state = actionGroup.get_action_state('name');
+        this._checkmark.opacity = this._name.equal(state);
+    }
+});
+
+function init() {
+}
+
+function buildPrefsWidget() {
+    let widget = new UserThemePrefsWidget();
+    widget.show_all();
+
+    return widget;
+}
diff --git a/extensions/user-theme/util.js b/extensions/user-theme/util.js
new file mode 100644
index 0000000..c0833b0
--- /dev/null
+++ b/extensions/user-theme/util.js
@@ -0,0 +1,12 @@
+/* exported getThemeDirs */
+const { GLib } = imports.gi;
+
+const fn = (...args) => GLib.build_filenamev(args);
+
+function getThemeDirs() {
+    return [
+        fn(GLib.get_home_dir(), '.themes'),
+        fn(GLib.get_user_data_dir(), 'themes'),
+        ...GLib.get_system_data_dirs().map(dir => fn(dir, 'themes')),
+    ];
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]