<NME> prefs.js
<BEF> var prefs = (function (Gtk3, Gtk4, Gio, GLib, GObject) {
    'use strict';

    const extensionUtils = imports.misc.extensionUtils;
    if (!extensionUtils.gettext) {
        // backport from v41
        // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/1deb13e1aaabfd04b2641976a224b6fc2be3b9ec/js/misc/extensionUtils.js#L117
        const domain = extensionUtils.getCurrentExtension().metadata['gettext-domain'];
        extensionUtils.initTranslations(domain);
        const gettextForDomain = imports.gettext.domain(domain);
        if (gettextForDomain.gettext) {
            Object.assign(extensionUtils, gettextForDomain);
        }
        else {
            logError(new Error(`could create gettextForDomain domain=${domain}`));
        }
    }
    const _ = extensionUtils.gettext;

    const PATH_SEPARATOR = '/';
    function join(...segments) {
        return [''].concat(segments.filter((e) => e !== '')).join(PATH_SEPARATOR);
    }
    function expandUserDir(segment) {
        switch (segment.toUpperCase()) {
            case '$PICTURES':
                return GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES);
            default:
                return segment;
        }
    }
    function expand(path) {
        return join(...path.split(PATH_SEPARATOR).map(expandUserDir));
    }

    function getGtkVersion() {
        const v = Gtk3.get_major_version();
        if (v === 3 || v === 4) {
            return v;
        }
        throw new Error('unsupported version');
    }
    function getCompatRoot(w) {
        switch (getGtkVersion()) {
            case 3:
                return w.get_toplevel();
            case 4:
    const KeyImgurAutoCopyLink = 'imgur-auto-copy-link';
    const KeyImgurAutoOpenLink = 'imgur-auto-open-link';
    const KeyEffectRescale = 'effect-rescale';

    function bindSensitivity(source, target) {
        const set = () => {
            }
        }
        throw new Error(`unknown class ${vc}`);
    }
    function getGObjectTypeFromGVariantClass(vc) {
        switch (vc) {
            case GLib.VariantClass.INT16:
            case GLib.VariantClass.INT32:
            case GLib.VariantClass.INT64:
            case GLib.VariantClass.UINT16:
            case GLib.VariantClass.UINT32:
            case GLib.VariantClass.UINT64:
                return GObject.TYPE_INT;
            case GLib.VariantClass.STRING:
                return GObject.TYPE_STRING;
        }
        throw new Error(`unsupported GVariantClass ${getGVariantClassName(vc)}`);
    }
    function wrapGVariant(v) {
        switch (typeof v) {
            case 'boolean':
                return GLib.Variant.new_boolean(v);
            case 'number':
                return GLib.Variant.new_int32(v);
            case 'string':
                return GLib.Variant.new_string(v);
        }
        throw new Error(`could not find variant fo ${typeof v}`);
    }
    function unwrapGVariant(v) {
        switch (v.classify()) {
            case GLib.VariantClass.ARRAY:
                throw new Error('not supported');
            case GLib.VariantClass.BOOLEAN:
                return v.get_boolean();
            case GLib.VariantClass.BYTE:
                return v.get_byte();
            case GLib.VariantClass.DICT_ENTRY:
                throw new Error('not supported');
            case GLib.VariantClass.DOUBLE:
                return v.get_double();
            case GLib.VariantClass.HANDLE:
                throw new Error('not supported');
            case GLib.VariantClass.MAYBE:
                throw new Error('not supported');
        });
        return comboBox;
    }
    function buildConfigRow(label, widget) {
        const hbox = buildHbox();
        hbox.add(label);
        hbox.add(widget);
        return hbox;
    }
    function buildConfigSwitch(settings, label, configKey) {
        const gtkLabel = new Gtk.Label({
            label,
            xalign: 0,
            expand: true,
        });
        const gtkSwitch = new Gtk.Switch();
        gtkSwitch.connect('notify::active', (button) => {
            settings.set_boolean(configKey, button.active);
        });
        gtkSwitch.active = settings.get_boolean(configKey);
        return {
            hbox: buildConfigRow(gtkLabel, gtkSwitch),
            gtkLabel,
            gtkSwitch,
        };
    }
        });
    }
    function syncSetting(settings, key, callback) {
        settings.connect('changed::' + key, () => {
            callback(unwrapGVariant(settings.get_value(key)));
        });
        callback(unwrapGVariant(settings.get_value(key)));
        const switchShowNotification = buildConfigSwitch(settings, _('Show Notification After Capture'), KeyEnableNotification);
        prefs.add(switchShowNotification.hbox);
        /* Default click action [dropdown] */
        const labelDefaultClickAction = new Gtk.Label({
            label: _('Primary Button'),
            xalign: 0,
            expand: true,
        });
        const clickActionOptions = [
            [_('Select Area'), ClickActions.SELECT_AREA],
            [_('Select Window'), ClickActions.SELECT_WINDOW],
            return unwrapGVariant(this.settings.get_value(key));
        }
        setValue(key, value) {
            this.settings.set_value(key, wrapGVariant(value));
        }
        getDefaultValue(key) {
            const defaultValue = this.settings.get_default_value(key);
            if (defaultValue === null) {
                throw new Error();
            }
            [_('Local Path'), ClipboardActions.SET_LOCAL_PATH],
        ];
        const clipboardActionDropdown = (label, { options, configKey }) => {
            const labelAutoCopy = new Gtk.Label({
                label,
                xalign: 0,
                expand: true,
            });
            const currentValue = settings.get_string(configKey);
            const comboBoxClipboardContent = getComboBox(options, GObject.TYPE_STRING, currentValue, (value) => settings.set_string(configKey, value));
            prefs.add(buildConfigRow(labelAutoCopy, comboBoxClipboardContent));
        };
        clipboardActionDropdown(_('Copy Button'), {
            options: [optionImageData, optionLocalPath],
            });
            return w;
        }
        buildComboBox(p) {
            const defaultValue = this.settings.get_default_value(p.settingsKey);
            if (!defaultValue) {
                throw new Error(`settings ${p.settingsKey} needs default value`);
            }
            if (!defaultValue.classify()) {
    function getPage$1(settings) {
        const prefs = buildPage();
        /* Rescale [dropdown] */
        const labelRescale = new Gtk.Label({
            label: _('Rescale'),
            xalign: 0,
            expand: true,
        });
        const rescaleOptions = [
            ['100%', 100],
            ['50%', 50],
            const renderer = new Gtk4.CellRendererText();
            comboBox.pack_start(renderer, true);
            comboBox.add_attribute(renderer, 'text', 0);
            for (const [label, value] of p.options) {
                const iter = model.append();
                model.set(iter, [Columns.LABEL, Columns.VALUE], [label, value]);
            }
            comboBox.connect('changed', () => {
                const [success, iter] = comboBox.get_active_iter();
                if (!success) {
                    return;
                }
                const value = model.get_value(iter, Columns.VALUE);
                this.setValue(p.settingsKey, value);
            });
            const setActiveByValue = (v) => {
                const [success, iter] = model.get_iter_first();
                if (!success) {
                    return;
                }
                for (;;) {
                    if (model.get_value(iter, Columns.VALUE) === v) {
                        comboBox.set_active_iter(iter);
                    }
                    if (!model.iter_next(iter)) {
                        return;
                    }
                }
            };
            syncSetting(this.settings, p.settingsKey, (v) => {
                setActiveByValue(v);
            });
            return comboBox;
        }
        buildEntry(p) {
            const w = new Gtk4.Entry({
                hexpand: true,
                tooltip_text: p.tooltip,
                secondary_icon_name: 'document-revert',
            });
            syncSetting(this.settings, p.settingsKey, (v) => {
                if (w.text !== v) {
                    w.text = v;
                }
            });
            w.get_buffer().connect('notify::text', ({ text }) => {
                if (p.validate(text)) {
                    this.setValue(p.settingsKey, text);
                    w.get_style_context().remove_class('error');
                }
                else {
                    w.get_style_context().add_class('error');
                }
            });
            w.connect('icon-press', () => {
                w.text = this.getDefaultValue(p.settingsKey);
            });
            return w;
        }
        buildFileChooser(p) {
            const w = new Gtk4.Button();
            syncSetting(this.settings, p.settingsKey, (path) => {
                const f = Gio.File.new_for_path(expand(path));
                w.label = f.get_basename() || p.label;
            });
            w.connect('clicked', () => {
                const d = new Gtk4.FileChooserDialog({
                    title: p.label,
                    action: Gtk4.FileChooserAction.SELECT_FOLDER,
                    transient_for: getCompatRoot(w),
                    modal: true,
                });
                d.add_button(p.label, Gtk4.ResponseType.OK);
                d.add_button(_('Cancel'), Gtk4.ResponseType.CANCEL);
                d.connect('response', (_dialog, response) => {
                    if (response === Gtk4.ResponseType.OK) {
                        this.setValue(p.settingsKey, d.get_file().get_path());
                    }
                    d.close();
                });
                d.show();
            });
            return w;
        }
        buildPreview(p) {
            const w = new Gtk4.Label();
            syncSetting(this.settings, p.settingsKey, () => {
                try {
                    w.label = p.format(this.settings);
                    w.get_style_context().remove_class('error');
                }
                catch (e) {
                    w.label = 'Error';
                    w.get_style_context().add_class('error');
                }
            });
            return w;
        }
        buildPrefWidget(w) {
            switch (w.type) {
                case 'Switch':
                    return this.buildSwitch(w);
                case 'ComboBox':
                    return this.buildComboBox(w);
                case 'Entry':
                    return this.buildEntry(w);
                case 'FileChooser':
                    return this.buildFileChooser(w);
                case 'Preview':
                    return this.buildPreview(w);
            }
            throw new Error('unknown type');
        }
    }.call(commonjsGlobal, commonjsGlobal);
    });

    const parameters = ({ width, height }) => {
        const now = new Date();
        const hostname = GLib.get_host_name();
        const padZero = (s, n) => {
                if (binding) {
                    switch (getGtkVersion()) {
                        case 3:
                            [key, mods] = Gtk3.accelerator_parse(binding);
                            break;
                        case 4:
                            const [success, ...parsed] = Gtk4.accelerator_parse(binding);
                            if (!success) {
                                throw new Error(`could not parse ${binding}`);
                            }
                            [key, mods] = parsed;
                    }
                }
                else {
                    [key, mods] = [0, 0];
                }
                const row = model.append();
            ['h', height, _('Height')],
            ['hn', hostname, _('Hostname')],
        ];
    };
    const tooltipText = (dim) => {
        const head = [_('Parameters:')];
        return parameters(dim)
            .reduce((arr, [key, _value, description]) => {
            arr.push(key + '\t' + description);
            return arr;
        }, head)
            .join('\n');
    };
    const get = (template, dim, n) => {
        const vars = parameters(dim).reduce((obj, [key, value]) => {
            obj[key] = value;
            return obj;
        }, {});
        const basename = stringFormat(template, vars);
        let sequence = '';
        if (n > 0) {
            sequence = '_' + String(n);
        }
        return basename + sequence + '.png';
    };

    const PATH_SEPARATOR = '/';
    const join = (...segments) => {
                const cellrend = new Gtk4.CellRendererAccel({
                    editable: true,
                    accel_mode: Gtk4.CellRendererAccelMode.GTK,
                });
                cellrend.connect('accel-edited', (rend, path, key, mods) => {
                    const value = Gtk4.accelerator_name(key, mods);
                    const [succ, iterator] = model.get_iter_from_string(path);
                    if (!succ) {
                        throw new Error('Error updating keybinding');
                    }
                    const name = model.get_value(iterator, ColumnConfigKey);
        return join(...path.split(PATH_SEPARATOR).map(expandUserDir));
    };

    function getPage$2(settings) {
        const prefs = buildPage();
        /* Save Screenshot [on|off] */
        const switchSaveScreenshot = buildConfigSwitch(settings, _('Auto-Save Screenshot'), KeySaveScreenshot);
        prefs.add(switchSaveScreenshot.hbox);
        /* Save Location [filechooser] */
        const labelSaveLocation = new Gtk.Label({
            label: _('Save Location'),
            xalign: 0,
            expand: true,
        });
        const chooserSaveLocation = new Gtk.FileChooserButton({
            title: _('Select'),
            local_only: true,
                col.pack_end(cellrend, false);
                col.add_attribute(cellrend, 'accel-mods', ColumnShortcutModifiers);
                col.add_attribute(cellrend, 'accel-key', ColumnShortcutKey);
                treeview.append_column(col);
            }
            return treeview;
        }
    }
    function buildPrefPages(pages, settings, window) {
        const builder = new PrefBuilder(settings, window);
        const notebook = new Gtk4.Notebook();
        pages.forEach((p) => {
            const { label } = p;
            if ('rows' in p) {
                notebook.append_page(builder.buildPageRows(p.rows), new Gtk4.Label({ label }));
            }
            if ('widget' in p) {
            }
            settings.set_string(KeySaveLocation, filename);
        });
        bindSensitivity(switchSaveScreenshot.gtkSwitch, labelSaveLocation);
        bindSensitivity(switchSaveScreenshot.gtkSwitch, chooserSaveLocation);
        prefs.add(buildConfigRow(labelSaveLocation, chooserSaveLocation));
        /* Filename */
        const [defaultTemplate] = settings.get_default_value(KeyFilenameTemplate).get_string();
        const mockDimensions = { width: 800, height: 600 };
        const labelFilenameTemplate = new Gtk.Label({
            label: _('Default Filename'),
            xalign: 0,
            expand: true,
        });
        const inputFilenameTemplate = new Gtk.Entry({
            expand: true,
            tooltip_text: tooltipText(mockDimensions),
            secondary_icon_name: 'document-revert',
        });
        inputFilenameTemplate.text = settings.get_string(KeyFilenameTemplate);
        prefs.add(buildConfigRow(labelFilenameTemplate, inputFilenameTemplate));
        /* Filename Preview */
        const labelPreview = new Gtk.Label({
            label: _('Preview'),
            expand: true,
            xalign: 0,
        });
        const textPreview = new Gtk.Label({
            xalign: 0,
        });
    function commonjsRequire () {
    	throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs');
    }

    var stringFormat = createCommonjsModule(function (module) {
    void function(global) {

      //  ValueError :: String -> Error
      function ValueError(message) {
        var err = new Error(message);
        err.name = 'ValueError';
        return err;
      }

      //  create :: Object -> String,*... -> String
      function create(transformers) {
        return function(template) {
          var args = Array.prototype.slice.call(arguments, 1);
          var idx = 0;
          var state = 'UNDEFINED';

          return template.replace(
            /([{}])\1|[{](.*?)(?:!(.+?))?[}]/g,
            function(match, literal, _key, xf) {
              if (literal != null) {
                return literal;
        return prefs;
    }

    function getPage$3(settings) {
        const prefs = buildPage();
        /* Enable Imgur Upload [on|off] */
        const configSwitchEnable = buildConfigSwitch(settings, _('Enable Imgur Upload'), KeyEnableUploadImgur);
                state = 'EXPLICIT';
        /* Enable Upload Notification [on|off] */
        const configSwitchEnableNotification = buildConfigSwitch(settings, _('Show Upload Notification'), KeyImgurEnableNotification);
        prefs.add(configSwitchEnableNotification.hbox);
        bindSensitivity(configSwitchEnable.gtkSwitch, configSwitchEnableNotification.gtkLabel);
        bindSensitivity(configSwitchEnable.gtkSwitch, configSwitchEnableNotification.gtkSwitch);
        /* Auto-Upload After Capture [on|off] */
        const configSwitchUploadOnCapture = buildConfigSwitch(settings, _('Auto-Upload After Capture'), KeyImgurAutoUpload);
        bindSensitivity(configSwitchEnable.gtkSwitch, configSwitchUploadOnCapture.gtkLabel);
        bindSensitivity(configSwitchEnable.gtkSwitch, configSwitchUploadOnCapture.gtkSwitch);
        prefs.add(configSwitchUploadOnCapture.hbox);
        /* Auto-Copy Link After Upload [on|off] */
        const configSwitchCopyLinkOnUpload = buildConfigSwitch(settings, _('Auto-Copy Link After Upload'), KeyImgurAutoCopyLink);
        bindSensitivity(configSwitchEnable.gtkSwitch, configSwitchCopyLinkOnUpload.gtkLabel);
        bindSensitivity(configSwitchEnable.gtkSwitch, configSwitchCopyLinkOnUpload.gtkSwitch);
        prefs.add(configSwitchCopyLinkOnUpload.hbox);
        /* Auto-Open Link After Upload [on|off] */
        const configSwitchOpenLinkOnUpload = buildConfigSwitch(settings, _('Auto-Open Link After Upload'), KeyImgurAutoOpenLink);
        bindSensitivity(configSwitchEnable.gtkSwitch, configSwitchOpenLinkOnUpload.gtkLabel);
        bindSensitivity(configSwitchEnable.gtkSwitch, configSwitchOpenLinkOnUpload.gtkSwitch);
        prefs.add(configSwitchOpenLinkOnUpload.hbox);
        return prefs;
    }

    // accelerator setting based on
    // https://github.com/ambrice/spatialnavigation-tastycactus.com/blob/master/prefs.js
    function getPage$4(settings) {
        const model = new Gtk.ListStore();
        model.set_column_types([GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_INT, GObject.TYPE_INT]);
        const bindings = [
                .reduce(function(_, x) { return x; }, '');

              if (xf == null) {
                return value;
              } else if (Object.prototype.hasOwnProperty.call(transformers, xf)) {
                return transformers[xf](value);
              } else {
                throw ValueError('no transformer named "' + xf + '"');
              }
            }
          );
        };
      }

      //  format :: String,*... -> String
      var format = create({});

      //  format.create :: Object -> String,*... -> String
      format.create = create;

      //  format.extend :: Object,Object -> ()
      format.extend = function(prototype, transformers) {
        var $format = create(transformers);
        prototype.format = function() {
          var args = Array.prototype.slice.call(arguments);
          args.unshift(this);
          return $format.apply(global, args);
        };
      };

      /* istanbul ignore else */
      {
        module.exports = format;
      }

    }.call(commonjsGlobal, commonjsGlobal);
    });

    function toTooltipText(parameters, caption = _('Parameters:')) {
        return parameters
            .reduce((arr, [key, _value, description]) => {
            arr.push(key + '\t' + description);
            return arr;
        }, [caption])
            .join('\n');
    }
    function toObject(parameters) {
        return parameters.reduce((obj, [key, value]) => {
            obj[key] = value;
            return obj;
        }, {});
    }

    function isValidTemplate(s) {
        try {
            stringFormat(s);
            return true;
        }
        catch (e) {
            return false;
        }
    }
            super._init(params);
            const settings = ExtensionUtils.getSettings();
            const notebook = new Gtk.Notebook();
            let page, label;
            page = getPage(settings);
            label = new Gtk.Label({ label: _('Indicator') });
            notebook.append_page(page, label);
            page = getPage$1(settings);
            label = new Gtk.Label({ label: _('Effects') });
            notebook.append_page(page, label);
            page = getPage$2(settings);
            label = new Gtk.Label({ label: _('Storage') });
            notebook.append_page(page, label);
            page = getPage$3(settings);
            label = new Gtk.Label({ label: _('Imgur Upload (Beta)') });
            notebook.append_page(page, label);
            page = getPage$4(settings);
            label = new Gtk.Label({ label: _('Keybindings') });
            notebook.append_page(page, label);
            this.add(notebook);
        }
    }, Gtk.Box);
                return s;
            }
        };
        const pad = (s) => padZero(s, 2);
        return [
            ['N', _('Screenshot'), _('Screenshot (literal)')],
            ['Y', now.getFullYear(), _('Year')],
            ['m', pad(now.getMonth() + 1), _('Month')],
            ['d', pad(now.getDate()), _('Day')],
            ['H', pad(now.getHours()), _('Hour')],
            ['M', pad(now.getMinutes()), _('Minute')],
            ['S', pad(now.getSeconds()), _('Second')],
            ['w', width, _('Width')],
            ['h', height, _('Height')],
            ['hn', hostname, _('Hostname')],
        ];
    }
    function tooltipText$1(vars) {
        return toTooltipText(parameters$1(vars));
    }
    function get(template, vars, n) {
        const basename = stringFormat(template, toObject(parameters$1(vars)));
        let sequence = '';
        if (n && n > 0) {
            sequence = '_' + String(n);
        }
        return basename + sequence + '.png';
    }
    function isValidTemplate$1(template) {
        try {
            stringFormat(template);
            return true;
        }
        catch (e) {
            return false;
        }
    }

    const KeyBackend = 'backend';
    const Backends = {
        DESKTOP_PORTAL: 'desktop-portal',
        GNOME_SCREENSHOT_CLI: 'gnome-screenshot',
    };
    const KeyEnableIndicator = 'enable-indicator';
    const KeyEnableNotification = 'enable-notification';
    const ValueShortcutSelectArea = 'shortcut-select-area';
    const ValueShortcutSelectWindow = 'shortcut-select-window';
    const ValueShortcutSelectDesktop = 'shortcut-select-desktop';
    const ValueShortcutOpenPortal = 'shortcut-open-portal';
    // See schemas/org.gnome.shell.extensions.screenshot.gschema.xml
    const KeyClickAction = 'click-action';
    const ClickActions = {
        SHOW_MENU: 'show-menu',
        SELECT_AREA: 'select-area',
        SELECT_WINDOW: 'select-window',
        SELECT_DESKTOP: 'select-desktop',
        OPEN_PORTAL: 'open-portal',
    };
    const KeySaveScreenshot = 'save-screenshot';
    const KeySaveLocation = 'save-location';
    const KeyFilenameTemplate = 'filename-template';
    // "Auto-Copy to Clipboard" action
    const KeyClipboardAction = 'clipboard-action';
    // Copy button action
    const KeyCopyButtonAction = 'copy-button-action';
    const ClipboardActions = {
        NONE: 'none',
        SET_IMAGE_DATA: 'set-image-data',
        SET_LOCAL_PATH: 'set-local-path',
        SET_REMOTE_URL: 'set-remote-url',
    };
    const KeyEnableUploadImgur = 'enable-imgur';
    const KeyImgurEnableNotification = 'imgur-enable-notification';
    const KeyImgurAutoUpload = 'imgur-auto-upload';
    const KeyImgurAutoCopyLink = 'imgur-auto-copy-link';
    const KeyImgurAutoOpenLink = 'imgur-auto-open-link';
    const KeyEffectRescale = 'effect-rescale';
    const KeyEnableRunCommand = 'enable-run-command';
    const KeyRunCommand = 'run-command';

    function prefSwitch(settingsKey) {
        return {
            type: 'Switch',
            settingsKey,
        };
    }
    function prefComboBox(options, settingsKey) {
        return {
            type: 'ComboBox',
            options,
            settingsKey,
        };
    }
    function prefFileChooser(label, settingsKey) {
        return {
            type: 'FileChooser',
            label,
            settingsKey,
        };
    }
    function prefEntry(opts, settingsKey, validate) {
        return {
            type: 'Entry',
            settingsKey,
            tooltip: opts.tooltip,
            validate,
        };
    }
    function prefPreview(settingsKey, format) {
        return {
            type: 'Preview',
            settingsKey,
            format,
        };
    }
    function enableKey(k) {
        return function (s) {
            return s.get_boolean(k);
        };
    }
    function prefRow(label, widget, { enable = () => true } = {}) {
        return {
            label,
            widget,
            enable,
        };
    }
    function prefKeybinding(label, settingsKey) {
        return { label, settingsKey };
    }
    function prefKeybindings(bindings) {
        return {
            type: 'Keybindings',
            bindings,
        };
    }
    function prefPage(label, child) {
        if (Array.isArray(child)) {
            return { label, rows: child };
        }
        return { label, widget: child };
    }

    function getBackendPrefs() {
        const comboBoxOptions = [
            [_('gnome-screenshot'), Backends.GNOME_SCREENSHOT_CLI],
            [_('Desktop Portal'), Backends.DESKTOP_PORTAL],
        ];
        return [prefRow(_('Backend'), prefComboBox(comboBoxOptions, KeyBackend))];
    }
    function getIndicatorPrefs() {
        const [optionNothing, optionImageData, optionLocalPath] = [
            [_('Nothing'), ClipboardActions.NONE],
            [_('Image Data'), ClipboardActions.SET_IMAGE_DATA],
            [_('Local Path'), ClipboardActions.SET_LOCAL_PATH],
        ];
        return [
            prefRow(_('Show Indicator'), prefSwitch(KeyEnableIndicator)),
            prefRow(_('Show Notification After Capture'), prefSwitch(KeyEnableNotification)),
            prefRow(_('Primary Button'), prefComboBox([
                [_('Select Area'), ClickActions.SELECT_AREA],
                [_('Select Window'), ClickActions.SELECT_WINDOW],
                [_('Select Desktop'), ClickActions.SELECT_DESKTOP],
                [_('Open Portal'), ClickActions.OPEN_PORTAL],
                [_('Show Menu'), ClickActions.SHOW_MENU],
            ], KeyClickAction)),
            prefRow(_('Copy Button'), prefComboBox([optionImageData, optionLocalPath], KeyCopyButtonAction)),
            prefRow(_('Auto-Copy to Clipboard'), prefComboBox([optionNothing, optionImageData, optionLocalPath], KeyClipboardAction)),
        ];
    }
    function getEffectPrefs() {
        return [
            prefRow(_('Rescale'), prefComboBox([
                ['100%', 100],
                ['50%', 50],
            ], KeyEffectRescale)),
        ];
    }
    function getCommandPrefs() {
        return [
            prefRow(_('Run Command After Capture'), prefSwitch(KeyEnableRunCommand)),
            prefRow(_('Command'), prefEntry({ tooltip: tooltipText() }, KeyRunCommand, isValidTemplate), {
                enable: enableKey(KeyEnableRunCommand),
            }),
        ];
    }
    function getStoragePrefs() {
        const mockDimensions = { width: 800, height: 600 };
        return [
            prefRow(_('Auto-Save Screenshot'), prefSwitch(KeySaveScreenshot)),
            prefRow(_('Save Location'), prefFileChooser(_('Select'), KeySaveLocation), {
                enable: enableKey(KeySaveScreenshot),
            }),
            prefRow(_('Default Filename'), prefEntry({ tooltip: tooltipText$1(mockDimensions) }, KeyFilenameTemplate, isValidTemplate$1)),
            prefRow(_('Preview'), prefPreview(KeyFilenameTemplate, (settings) => {
                return get(settings.get_string(KeyFilenameTemplate), mockDimensions);
            })),
        ];
    }
    function getImgurPrefs() {
        const enableIfImgurEnabled = {
            enable: enableKey(KeyEnableUploadImgur),
        };
        return [
            prefRow(_('Enable Imgur Upload'), prefSwitch(KeyEnableUploadImgur)),
            prefRow(_('Show Upload Notification'), prefSwitch(KeyImgurEnableNotification), enableIfImgurEnabled),
            prefRow(_('Auto-Upload After Capture'), prefSwitch(KeyImgurAutoUpload), enableIfImgurEnabled),
            prefRow(_('Auto-Copy Link After Upload'), prefSwitch(KeyImgurAutoCopyLink), enableIfImgurEnabled),
            prefRow(_('Auto-Open Link After Upload'), prefSwitch(KeyImgurAutoOpenLink), enableIfImgurEnabled),
        ];
    }
    function getKeybindPrefs() {
        return prefKeybindings([
            prefKeybinding(_('Select area'), ValueShortcutSelectArea),
            prefKeybinding(_('Select window'), ValueShortcutSelectWindow),
            prefKeybinding(_('Select whole desktop'), ValueShortcutSelectDesktop),
            prefKeybinding(_('Open portal'), ValueShortcutOpenPortal),
        ]);
    }
    function getPages() {
        return [
            prefPage(_('Indicator'), getIndicatorPrefs()),
            prefPage(_('Effects'), getEffectPrefs()),
            prefPage(_('Commands'), getCommandPrefs()),
            prefPage(_('Storage'), getStoragePrefs()),
            prefPage(_('Imgur'), getImgurPrefs()),
            prefPage(_('Keybindings'), getKeybindPrefs()),
            prefPage(_('Backend'), getBackendPrefs()),
        ];
    }

    function init() {
        extensionUtils.initTranslations();
    }
    function buildPrefsWidget() {
        return buildPrefPages(getPages(), extensionUtils.getSettings(), null);
    }
    var prefs = { init, buildPrefsWidget };

    return prefs;

}(imports.gi.Gtk, imports.gi.Gtk, imports.gi.Gio, imports.gi.GLib, imports.gi.GObject));

var init = prefs.init;
var buildPrefsWidget = prefs.buildPrefsWidget;

<MSG> Update dist/

<DFF> @@ -48,6 +48,8 @@ var prefs = (function (Gtk, GObject, Gio, GLib) {
     const KeyImgurAutoCopyLink = 'imgur-auto-copy-link';
     const KeyImgurAutoOpenLink = 'imgur-auto-open-link';
     const KeyEffectRescale = 'effect-rescale';
+    const KeyEnableRunCommand = 'enable-run-command';
+    const KeyRunCommand = 'run-command';
 
     function bindSensitivity(source, target) {
         const set = () => {
@@ -99,26 +101,30 @@ var prefs = (function (Gtk, GObject, Gio, GLib) {
         });
         return comboBox;
     }
+    function buildLabel(label) {
+        return new Gtk.Label({
+            label,
+            xalign: 0,
+            expand: true,
+        });
+    }
     function buildConfigRow(label, widget) {
+        if (typeof label === 'string') {
+            return buildConfigRow(buildLabel(label), widget);
+        }
         const hbox = buildHbox();
         hbox.add(label);
         hbox.add(widget);
         return hbox;
     }
     function buildConfigSwitch(settings, label, configKey) {
-        const gtkLabel = new Gtk.Label({
-            label,
-            xalign: 0,
-            expand: true,
-        });
         const gtkSwitch = new Gtk.Switch();
         gtkSwitch.connect('notify::active', (button) => {
             settings.set_boolean(configKey, button.active);
         });
         gtkSwitch.active = settings.get_boolean(configKey);
         return {
-            hbox: buildConfigRow(gtkLabel, gtkSwitch),
-            gtkLabel,
+            hbox: buildConfigRow(label, gtkSwitch),
             gtkSwitch,
         };
     }
@@ -132,11 +138,7 @@ var prefs = (function (Gtk, GObject, Gio, GLib) {
         const switchShowNotification = buildConfigSwitch(settings, _('Show Notification After Capture'), KeyEnableNotification);
         prefs.add(switchShowNotification.hbox);
         /* Default click action [dropdown] */
-        const labelDefaultClickAction = new Gtk.Label({
-            label: _('Primary Button'),
-            xalign: 0,
-            expand: true,
-        });
+        const labelDefaultClickAction = _('Primary Button');
         const clickActionOptions = [
             [_('Select Area'), ClickActions.SELECT_AREA],
             [_('Select Window'), ClickActions.SELECT_WINDOW],
@@ -153,14 +155,9 @@ var prefs = (function (Gtk, GObject, Gio, GLib) {
             [_('Local Path'), ClipboardActions.SET_LOCAL_PATH],
         ];
         const clipboardActionDropdown = (label, { options, configKey }) => {
-            const labelAutoCopy = new Gtk.Label({
-                label,
-                xalign: 0,
-                expand: true,
-            });
             const currentValue = settings.get_string(configKey);
             const comboBoxClipboardContent = getComboBox(options, GObject.TYPE_STRING, currentValue, (value) => settings.set_string(configKey, value));
-            prefs.add(buildConfigRow(labelAutoCopy, comboBoxClipboardContent));
+            prefs.add(buildConfigRow(label, comboBoxClipboardContent));
         };
         clipboardActionDropdown(_('Copy Button'), {
             options: [optionImageData, optionLocalPath],
@@ -176,11 +173,7 @@ var prefs = (function (Gtk, GObject, Gio, GLib) {
     function getPage$1(settings) {
         const prefs = buildPage();
         /* Rescale [dropdown] */
-        const labelRescale = new Gtk.Label({
-            label: _('Rescale'),
-            xalign: 0,
-            expand: true,
-        });
+        const labelRescale = _('Rescale');
         const rescaleOptions = [
             ['100%', 100],
             ['50%', 50],
@@ -300,7 +293,44 @@ var prefs = (function (Gtk, GObject, Gio, GLib) {
     }.call(commonjsGlobal, commonjsGlobal);
     });
 
-    const parameters = ({ width, height }) => {
+    function toTooltipText(parameters, caption = _('Parameters:')) {
+        return parameters
+            .reduce((arr, [key, _value, description]) => {
+            arr.push(key + '\t' + description);
+            return arr;
+        }, [caption])
+            .join('\n');
+    }
+    function toObject(parameters) {
+        return parameters.reduce((obj, [key, value]) => {
+            obj[key] = value;
+            return obj;
+        }, {});
+    }
+
+    const settings = ExtensionUtils.getSettings();
+    function parameters(v) {
+        return [['f', GLib.shell_quote(v.filename), _('Filename')]];
+    }
+    function tooltipText() {
+        return toTooltipText(parameters({ filename: '/path/to/file.png' }));
+    }
+
+    function getPage$2(settings) {
+        const prefs = buildPage();
+        const configRowEnableRunCommand = buildConfigSwitch(settings, _('Run Command After Capture'), KeyEnableRunCommand);
+        prefs.add(configRowEnableRunCommand.hbox);
+        const configRowRunCommand = buildConfigRow(_('Command'), new Gtk.Entry({
+            expand: true,
+            tooltip_text: tooltipText(),
+            text: settings.get_string(KeyRunCommand),
+        }));
+        bindSensitivity(configRowEnableRunCommand.gtkSwitch, configRowRunCommand);
+        prefs.add(configRowRunCommand);
+        return prefs;
+    }
+
+    function parameters$1({ width, height }) {
         const now = new Date();
         const hostname = GLib.get_host_name();
         const padZero = (s, n) => {
@@ -324,28 +354,18 @@ var prefs = (function (Gtk, GObject, Gio, GLib) {
             ['h', height, _('Height')],
             ['hn', hostname, _('Hostname')],
         ];
-    };
-    const tooltipText = (dim) => {
-        const head = [_('Parameters:')];
-        return parameters(dim)
-            .reduce((arr, [key, _value, description]) => {
-            arr.push(key + '\t' + description);
-            return arr;
-        }, head)
-            .join('\n');
-    };
-    const get = (template, dim, n) => {
-        const vars = parameters(dim).reduce((obj, [key, value]) => {
-            obj[key] = value;
-            return obj;
-        }, {});
-        const basename = stringFormat(template, vars);
+    }
+    function tooltipText$1(vars) {
+        return toTooltipText(parameters$1(vars));
+    }
+    function get(template, vars, n) {
+        const basename = stringFormat(template, toObject(parameters$1(vars)));
         let sequence = '';
-        if (n > 0) {
+        if (n && n > 0) {
             sequence = '_' + String(n);
         }
         return basename + sequence + '.png';
-    };
+    }
 
     const PATH_SEPARATOR = '/';
     const join = (...segments) => {
@@ -363,17 +383,13 @@ var prefs = (function (Gtk, GObject, Gio, GLib) {
         return join(...path.split(PATH_SEPARATOR).map(expandUserDir));
     };
 
-    function getPage$2(settings) {
+    function getPage$3(settings) {
         const prefs = buildPage();
         /* Save Screenshot [on|off] */
         const switchSaveScreenshot = buildConfigSwitch(settings, _('Auto-Save Screenshot'), KeySaveScreenshot);
         prefs.add(switchSaveScreenshot.hbox);
         /* Save Location [filechooser] */
-        const labelSaveLocation = new Gtk.Label({
-            label: _('Save Location'),
-            xalign: 0,
-            expand: true,
-        });
+        const labelSaveLocation = _('Save Location');
         const chooserSaveLocation = new Gtk.FileChooserButton({
             title: _('Select'),
             local_only: true,
@@ -397,30 +413,22 @@ var prefs = (function (Gtk, GObject, Gio, GLib) {
             }
             settings.set_string(KeySaveLocation, filename);
         });
-        bindSensitivity(switchSaveScreenshot.gtkSwitch, labelSaveLocation);
-        bindSensitivity(switchSaveScreenshot.gtkSwitch, chooserSaveLocation);
-        prefs.add(buildConfigRow(labelSaveLocation, chooserSaveLocation));
+        const box = buildConfigRow(labelSaveLocation, chooserSaveLocation);
+        bindSensitivity(switchSaveScreenshot.gtkSwitch, box);
+        prefs.add(box);
         /* Filename */
         const [defaultTemplate] = settings.get_default_value(KeyFilenameTemplate).get_string();
         const mockDimensions = { width: 800, height: 600 };
-        const labelFilenameTemplate = new Gtk.Label({
-            label: _('Default Filename'),
-            xalign: 0,
-            expand: true,
-        });
+        const labelFilenameTemplate = _('Default Filename');
         const inputFilenameTemplate = new Gtk.Entry({
             expand: true,
-            tooltip_text: tooltipText(mockDimensions),
+            tooltip_text: tooltipText$1(mockDimensions),
             secondary_icon_name: 'document-revert',
         });
         inputFilenameTemplate.text = settings.get_string(KeyFilenameTemplate);
         prefs.add(buildConfigRow(labelFilenameTemplate, inputFilenameTemplate));
         /* Filename Preview */
-        const labelPreview = new Gtk.Label({
-            label: _('Preview'),
-            expand: true,
-            xalign: 0,
-        });
+        const labelPreview = _('Preview');
         const textPreview = new Gtk.Label({
             xalign: 0,
         });
@@ -453,7 +461,7 @@ var prefs = (function (Gtk, GObject, Gio, GLib) {
         return prefs;
     }
 
-    function getPage$3(settings) {
+    function getPage$4(settings) {
         const prefs = buildPage();
         /* Enable Imgur Upload [on|off] */
         const configSwitchEnable = buildConfigSwitch(settings, _('Enable Imgur Upload'), KeyEnableUploadImgur);
@@ -461,29 +469,25 @@ var prefs = (function (Gtk, GObject, Gio, GLib) {
         /* Enable Upload Notification [on|off] */
         const configSwitchEnableNotification = buildConfigSwitch(settings, _('Show Upload Notification'), KeyImgurEnableNotification);
         prefs.add(configSwitchEnableNotification.hbox);
-        bindSensitivity(configSwitchEnable.gtkSwitch, configSwitchEnableNotification.gtkLabel);
-        bindSensitivity(configSwitchEnable.gtkSwitch, configSwitchEnableNotification.gtkSwitch);
+        bindSensitivity(configSwitchEnable.gtkSwitch, configSwitchEnableNotification.hbox);
         /* Auto-Upload After Capture [on|off] */
         const configSwitchUploadOnCapture = buildConfigSwitch(settings, _('Auto-Upload After Capture'), KeyImgurAutoUpload);
-        bindSensitivity(configSwitchEnable.gtkSwitch, configSwitchUploadOnCapture.gtkLabel);
-        bindSensitivity(configSwitchEnable.gtkSwitch, configSwitchUploadOnCapture.gtkSwitch);
+        bindSensitivity(configSwitchEnable.gtkSwitch, configSwitchUploadOnCapture.hbox);
         prefs.add(configSwitchUploadOnCapture.hbox);
         /* Auto-Copy Link After Upload [on|off] */
         const configSwitchCopyLinkOnUpload = buildConfigSwitch(settings, _('Auto-Copy Link After Upload'), KeyImgurAutoCopyLink);
-        bindSensitivity(configSwitchEnable.gtkSwitch, configSwitchCopyLinkOnUpload.gtkLabel);
-        bindSensitivity(configSwitchEnable.gtkSwitch, configSwitchCopyLinkOnUpload.gtkSwitch);
+        bindSensitivity(configSwitchEnable.gtkSwitch, configSwitchCopyLinkOnUpload.hbox);
         prefs.add(configSwitchCopyLinkOnUpload.hbox);
         /* Auto-Open Link After Upload [on|off] */
         const configSwitchOpenLinkOnUpload = buildConfigSwitch(settings, _('Auto-Open Link After Upload'), KeyImgurAutoOpenLink);
-        bindSensitivity(configSwitchEnable.gtkSwitch, configSwitchOpenLinkOnUpload.gtkLabel);
-        bindSensitivity(configSwitchEnable.gtkSwitch, configSwitchOpenLinkOnUpload.gtkSwitch);
+        bindSensitivity(configSwitchEnable.gtkSwitch, configSwitchOpenLinkOnUpload.hbox);
         prefs.add(configSwitchOpenLinkOnUpload.hbox);
         return prefs;
     }
 
     // accelerator setting based on
     // https://github.com/ambrice/spatialnavigation-tastycactus.com/blob/master/prefs.js
-    function getPage$4(settings) {
+    function getPage$5(settings) {
         const model = new Gtk.ListStore();
         model.set_column_types([GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_INT, GObject.TYPE_INT]);
         const bindings = [
@@ -552,22 +556,15 @@ var prefs = (function (Gtk, GObject, Gio, GLib) {
             super._init(params);
             const settings = ExtensionUtils.getSettings();
             const notebook = new Gtk.Notebook();
-            let page, label;
-            page = getPage(settings);
-            label = new Gtk.Label({ label: _('Indicator') });
-            notebook.append_page(page, label);
-            page = getPage$1(settings);
-            label = new Gtk.Label({ label: _('Effects') });
-            notebook.append_page(page, label);
-            page = getPage$2(settings);
-            label = new Gtk.Label({ label: _('Storage') });
-            notebook.append_page(page, label);
-            page = getPage$3(settings);
-            label = new Gtk.Label({ label: _('Imgur Upload (Beta)') });
-            notebook.append_page(page, label);
-            page = getPage$4(settings);
-            label = new Gtk.Label({ label: _('Keybindings') });
-            notebook.append_page(page, label);
+            function addPage(label, page) {
+                notebook.append_page(page, new Gtk.Label({ label }));
+            }
+            addPage(_('Indicator'), getPage(settings));
+            addPage(_('Effects'), getPage$1(settings));
+            addPage(_('Commands'), getPage$2(settings));
+            addPage(_('Storage'), getPage$3(settings));
+            addPage(_('Imgur Upload'), getPage$4(settings));
+            addPage(_('Keybindings'), getPage$5(settings));
             this.add(notebook);
         }
     }, Gtk.Box);

Update dist/