import Adw from 'gi://Adw'; import GObject from 'gi://GObject'; import Gtk from 'gi://Gtk'; import Gdk from 'gi://Gdk'; import Gio from 'gi://Gio'; import { ExtensionPreferences, gettext as _ } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'; import { PrefsFields } from './constants.js'; export default class ClipboardIndicatorPreferences extends ExtensionPreferences { fillPreferencesWindow (window) { window._settings = this.getSettings(); const settingsUI = new Settings(window._settings); const page = new Adw.PreferencesPage(); page.add(settingsUI.ui); page.add(settingsUI.limits); page.add(settingsUI.topbar); page.add(settingsUI.notifications); page.add(settingsUI.shortcuts); window.add(page); } } class Settings { constructor (schema) { this.schema = schema; this.field_size = new Adw.SpinRow({ title: _("History Size"), adjustment: new Gtk.Adjustment({ lower: 1, upper: 200, step_increment: 1 }) }); this.field_preview_size = new Adw.SpinRow({ title: _("Preview Size (characters)"), adjustment: new Gtk.Adjustment({ lower: 10, upper: 100, step_increment: 1 }) }); this.field_cache_size = new Adw.SpinRow({ title: _("Max cache file size (MB)"), adjustment: new Gtk.Adjustment({ lower: 1, upper: 256, step_increment: 1 }) }); this.field_topbar_preview_size = new Adw.SpinRow({ title: _("Number of characters in top bar"), adjustment: new Gtk.Adjustment({ lower: 1, upper: 100, step_increment: 1 }) }); this.field_display_mode = new Adw.ComboRow({ title: _("What to show in top bar"), model: this.#createDisplayModeOptions() }); this.field_disable_down_arrow = new Adw.SwitchRow({ title: _("Remove down arrow in top bar") }); this.field_cache_disable = new Adw.SwitchRow({ title: _("Cache only pinned items") }); this.field_notification_toggle = new Adw.SwitchRow({ title: _("Show notification on copy") }); this.field_confirm_clear_toggle = new Adw.SwitchRow({ title: _("Show confirmation on Clear History") }); this.field_strip_text = new Adw.SwitchRow({ title: _("Remove whitespace around text") }); this.field_move_item_first = new Adw.SwitchRow({ title: _("Move item to the top after selection") }); this.field_keep_selected_on_clear = new Adw.SwitchRow({ title: _("Keep selected entry after Clear History") }); this.field_paste_button = new Adw.SwitchRow({ title: _("Show paste buttons"), subtitle: _("Adds a paste button to each entry that lets you paste it directly") }); this.field_pinned_on_bottom = new Adw.SwitchRow({ title: _("Place the pinned section on the bottom"), subtitle: _("Requires restarting the extension") }); this.ui = new Adw.PreferencesGroup({ title: _('UI') }); this.limits = new Adw.PreferencesGroup({ title: _('Limits') }); this.topbar = new Adw.PreferencesGroup({ title: _('Topbar') }); this.notifications = new Adw.PreferencesGroup({ title: _('Notifications') }); this.shortcuts = new Adw.PreferencesGroup({ title: _('Shortcuts') }); this.ui.add(this.field_preview_size); this.ui.add(this.field_move_item_first); this.ui.add(this.field_strip_text); this.ui.add(this.field_keep_selected_on_clear); this.ui.add(this.field_paste_button); this.ui.add(this.field_pinned_on_bottom); this.limits.add(this.field_size); this.limits.add(this.field_cache_size); this.limits.add(this.field_cache_disable); this.topbar.add(this.field_display_mode); this.topbar.add(this.field_topbar_preview_size); this.topbar.add(this.field_disable_down_arrow); this.notifications.add(this.field_notification_toggle); this.notifications.add(this.field_confirm_clear_toggle); this.#buildShorcuts(this.shortcuts); this.schema.bind(PrefsFields.HISTORY_SIZE, this.field_size, 'value', Gio.SettingsBindFlags.DEFAULT); this.schema.bind(PrefsFields.PREVIEW_SIZE, this.field_preview_size, 'value', Gio.SettingsBindFlags.DEFAULT); this.schema.bind(PrefsFields.CACHE_FILE_SIZE, this.field_cache_size, 'value', Gio.SettingsBindFlags.DEFAULT); this.schema.bind(PrefsFields.CACHE_ONLY_FAVORITE, this.field_cache_disable, 'active', Gio.SettingsBindFlags.DEFAULT); this.schema.bind(PrefsFields.NOTIFY_ON_COPY, this.field_notification_toggle, 'active', Gio.SettingsBindFlags.DEFAULT); this.schema.bind(PrefsFields.CONFIRM_ON_CLEAR, this.field_confirm_clear_toggle, 'active', Gio.SettingsBindFlags.DEFAULT); this.schema.bind(PrefsFields.MOVE_ITEM_FIRST, this.field_move_item_first, 'active', Gio.SettingsBindFlags.DEFAULT); this.schema.bind(PrefsFields.KEEP_SELECTED_ON_CLEAR, this.field_keep_selected_on_clear, 'active', Gio.SettingsBindFlags.DEFAULT); this.schema.bind(PrefsFields.TOPBAR_DISPLAY_MODE_ID, this.field_display_mode, 'selected', Gio.SettingsBindFlags.DEFAULT); this.schema.bind(PrefsFields.DISABLE_DOWN_ARROW, this.field_disable_down_arrow, 'active', Gio.SettingsBindFlags.DEFAULT); this.schema.bind(PrefsFields.TOPBAR_PREVIEW_SIZE, this.field_topbar_preview_size, 'value', Gio.SettingsBindFlags.DEFAULT); this.schema.bind(PrefsFields.STRIP_TEXT, this.field_strip_text, 'active', Gio.SettingsBindFlags.DEFAULT); this.schema.bind(PrefsFields.PASTE_BUTTON, this.field_paste_button, 'active', Gio.SettingsBindFlags.DEFAULT); this.schema.bind(PrefsFields.PINNED_ON_BOTTOM, this.field_pinned_on_bottom, 'active', Gio.SettingsBindFlags.DEFAULT); this.schema.bind(PrefsFields.ENABLE_KEYBINDING, this.field_keybinding_activation, 'active', Gio.SettingsBindFlags.DEFAULT); } #createDisplayModeOptions () { let options = [ _("Icon"), _("Clipboard Content"), _("Both") ]; let liststore = new Gtk.StringList(); for (let option of options) { liststore.append(option) } return liststore; } #shortcuts = { [PrefsFields.BINDING_PRIVATE_MODE]: _("Private mode"), [PrefsFields.BINDING_TOGGLE_MENU]: _("Toggle the menu"), [PrefsFields.BINDING_CLEAR_HISTORY]: _("Clear history"), [PrefsFields.BINDING_PREV_ENTRY]: _("Previous entry"), [PrefsFields.BINDING_NEXT_ENTRY]: _("Next entry") }; #buildShorcuts (group) { this.field_keybinding_activation = new Adw.SwitchRow({ title: _("Enable shortcuts") }); group.add(this.field_keybinding_activation); for (const [pref, title] of Object.entries(this.#shortcuts)) { const row = new Adw.ActionRow({ title }); row.add_suffix(this.#createShortcutButton(pref)); group.add(row); } } #createShortcutButton (pref) { const button = new Gtk.Button({ has_frame: false }); const setLabelFromSettings = () => { const originalValue = this.schema.get_strv(pref)[0]; if (!originalValue) { button.set_label(_('Disabled')); } else { button.set_label(originalValue); } }; const startEditing = () => { button.isEditing = button.label; button.set_label(_('Enter shortcut')); }; const revertEditing = () => { button.set_label(button.isEditing); button.isEditing = null; }; const stopEditing = () => { setLabelFromSettings(); button.isEditing = null; }; setLabelFromSettings(); button.connect('clicked', () => { if (button.isEditing) { revertEditing(); return; } startEditing(); const eventController = new Gtk.EventControllerKey(); button.add_controller(eventController); let debounceTimeoutId = null; const connectId = eventController.connect('key-pressed', (_ec, keyval, keycode, mask) => { if (debounceTimeoutId) clearTimeout(debounceTimeoutId); mask = mask & Gtk.accelerator_get_default_mod_mask(); if (mask === 0) { switch (keyval) { case Gdk.KEY_Escape: revertEditing(); return Gdk.EVENT_STOP; case Gdk.KEY_BackSpace: this.schema.set_strv(pref, []); setLabelFromSettings(); stopEditing(); eventController.disconnect(connectId); return Gdk.EVENT_STOP; } } const selectedShortcut = Gtk.accelerator_name_with_keycode( null, keyval, keycode, mask ); debounceTimeoutId = setTimeout(() => { eventController.disconnect(connectId); this.schema.set_strv(pref, [selectedShortcut]); stopEditing(); }, 400); return Gdk.EVENT_STOP; }); button.show(); }); return button; } }