From c5e665b59d07958aa1e3c6bbd498a535a27c77a5 Mon Sep 17 00:00:00 2001 From: IlCraccatore2011 Date: Sun, 25 Jan 2026 23:30:57 +0100 Subject: [PATCH 1/3] Updated languages, fixed search box theme bug, added new icon when there is clipboard in the system --- README.rst | 2 +- clipboard-indicator.pot | 140 +++++--- extension.js | 311 +++--------------- icons/edit-pasted-symbolic.svg | 8 + icons/edit-pasted-symbolic.svg.license | 3 + locale/de/LC_MESSAGES/clipboard-indicator.mo | Bin 3639 -> 4916 bytes locale/de/LC_MESSAGES/clipboard-indicator.po | 233 +++++++------ locale/es/LC_MESSAGES/clipboard-indicator.mo | Bin 3488 -> 4906 bytes locale/es/LC_MESSAGES/clipboard-indicator.po | 231 +++++++------ .../fr_FR/LC_MESSAGES/clipboard-indicator.mo | Bin 4241 -> 4943 bytes .../fr_FR/LC_MESSAGES/clipboard-indicator.po | 47 ++- locale/it/LC_MESSAGES/clipboard-indicator.mo | Bin 4092 -> 4820 bytes locale/it/LC_MESSAGES/clipboard-indicator.po | 161 +++++---- locale/ru/LC_MESSAGES/clipboard-indicator.mo | Bin 5150 -> 6174 bytes locale/ru/LC_MESSAGES/clipboard-indicator.po | 214 ++++++------ metadata.json | 2 +- 16 files changed, 664 insertions(+), 688 deletions(-) create mode 100644 icons/edit-pasted-symbolic.svg create mode 100644 icons/edit-pasted-symbolic.svg.license diff --git a/README.rst b/README.rst index 4fc78b63..9a26a27d 100644 --- a/README.rst +++ b/README.rst @@ -8,7 +8,7 @@ :alt: Get it on GNOME Extensions The most popular, reliable and feature-rich clipboard manager for GNOME with -over **1M** downloads. +over **2M** downloads. |Screenshot| diff --git a/clipboard-indicator.pot b/clipboard-indicator.pot index b10fb338..ad0783f7 100644 --- a/clipboard-indicator.pot +++ b/clipboard-indicator.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-09-21 16:41+0300\n" +"POT-Creation-Date: 2026-01-23 20:53+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,214 +17,246 @@ msgstr "" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" -#: extension.js:108 +#: extension.js:113 msgid "Text will be here" msgstr "" -#: extension.js:181 +#: extension.js:187 msgid "Type here to search..." msgstr "" -#: extension.js:250 prefs.js:220 +#: extension.js:256 prefs.js:257 msgid "Private mode" msgstr "" -#: extension.js:264 prefs.js:222 +#: extension.js:270 prefs.js:259 msgid "Clear history" msgstr "" -#: extension.js:276 +#: extension.js:315 msgid "Settings" msgstr "" -#: extension.js:299 +#: extension.js:338 msgid "Clipboard is empty" msgstr "" -#: extension.js:571 +#: extension.js:626 msgid "Clear all?" msgstr "" -#: extension.js:572 +#: extension.js:627 msgid "Are you sure you want to delete all clipboard items?" msgstr "" -#: extension.js:573 +#: extension.js:628 msgid "This operation cannot be undone." msgstr "" -#: extension.js:575 +#: extension.js:630 msgid "Clear" msgstr "" -#: extension.js:575 extension.js:740 +#: extension.js:630 extension.js:801 msgid "Cancel" msgstr "" -#: extension.js:588 +#: extension.js:645 msgid "Clipboard history cleared" msgstr "" -#: extension.js:739 +#: extension.js:648 +msgid "Clipboard history cleared automatically" +msgstr "" + +#: extension.js:800 msgid "Copied to clipboard" msgstr "" -#: prefs.js:30 +#: prefs.js:31 msgid "History Size" msgstr "" -#: prefs.js:39 +#: prefs.js:40 msgid "Preview Size (characters)" msgstr "" -#: prefs.js:48 +#: prefs.js:49 msgid "Max cache file size (MB)" msgstr "" -#: prefs.js:57 +#: prefs.js:58 msgid "Number of characters in top bar" msgstr "" -#: prefs.js:66 +#: prefs.js:67 msgid "What to show in top bar" msgstr "" -#: prefs.js:71 +#: prefs.js:72 msgid "Remove down arrow in top bar" msgstr "" -#: prefs.js:75 +#: prefs.js:76 msgid "Cache only pinned items" msgstr "" -#: prefs.js:79 +#: prefs.js:80 msgid "Show notification on copy" msgstr "" -#: prefs.js:83 +#: prefs.js:84 msgid "Show notification on cycle" msgstr "" -#: prefs.js:87 +#: prefs.js:88 msgid "Show confirmation on Clear History" msgstr "" -#: prefs.js:91 +#: prefs.js:92 msgid "Remove whitespace around text" msgstr "" -#: prefs.js:95 +#: prefs.js:96 msgid "Move item to the top after selection" msgstr "" -#: prefs.js:99 +#: prefs.js:100 msgid "Keep selected entry after Clear History" msgstr "" -#: prefs.js:103 +#: prefs.js:104 msgid "Show paste buttons" msgstr "" -#: prefs.js:104 +#: prefs.js:105 msgid "Adds a paste button to each entry that lets you paste it directly" msgstr "" -#: prefs.js:108 +#: prefs.js:109 msgid "Place the pinned section on the bottom" msgstr "" -#: prefs.js:109 +#: prefs.js:110 msgid "Requires restarting the extension" msgstr "" -#: prefs.js:113 +#: prefs.js:114 msgid "Clear clipboard history on system reboot" msgstr "" -#: prefs.js:117 +#: prefs.js:118 msgid "Paste on select" msgstr "" -#: prefs.js:121 +#: prefs.js:122 msgid "Cache images" msgstr "" -#: prefs.js:126 +#: prefs.js:127 msgid "Excluded Apps" msgstr "" -#: prefs.js:127 +#: prefs.js:128 msgid "Content copied will not be saved while these apps are in focus" msgstr "" -#: prefs.js:146 +#: prefs.js:139 +msgid "Case-sensitive search" +msgstr "" + +#: prefs.js:143 +msgid "Regular expression matching in search" +msgstr "" + +#: prefs.js:156 +msgid "Clear clipboard history on interval" +msgstr "" + +#: prefs.js:160 +msgid "History clear interval (in minutes)" +msgstr "" + +#: prefs.js:172 msgid "UI" msgstr "" -#: prefs.js:147 +#: prefs.js:173 msgid "Behavior" msgstr "" -#: prefs.js:148 +#: prefs.js:174 msgid "Exclusion" msgstr "" -#: prefs.js:149 +#: prefs.js:175 msgid "Limits" msgstr "" -#: prefs.js:150 +#: prefs.js:176 msgid "Topbar" msgstr "" -#: prefs.js:151 +#: prefs.js:177 msgid "Notifications" msgstr "" -#: prefs.js:152 +#: prefs.js:178 msgid "Shortcuts" msgstr "" -#: prefs.js:207 +#: prefs.js:179 +msgid "Search" +msgstr "" + +#: prefs.js:244 msgid "Icon" msgstr "" -#: prefs.js:208 +#: prefs.js:245 msgid "Clipboard Content" msgstr "" -#: prefs.js:209 +#: prefs.js:246 msgid "Both" msgstr "" -#: prefs.js:210 +#: prefs.js:247 msgid "Neither" msgstr "" -#: prefs.js:221 +#: prefs.js:258 msgid "Toggle the menu" msgstr "" -#: prefs.js:223 +#: prefs.js:260 msgid "Previous entry" msgstr "" -#: prefs.js:224 +#: prefs.js:261 msgid "Next entry" msgstr "" -#: prefs.js:229 +#: prefs.js:266 msgid "Enable shortcuts" msgstr "" -#: prefs.js:254 +#: prefs.js:291 msgid "Disabled" msgstr "" -#: prefs.js:263 +#: prefs.js:300 msgid "Enter shortcut" msgstr "" -#: prefs.js:345 +#: prefs.js:387 msgid "Window class name, e.g. \"KeePassXC\"" msgstr "" + +#: prefs.js:399 +msgid "Choose from installed applications" +msgstr "" + +#: prefs.js:416 +msgid "Search applications..." +msgstr "" diff --git a/extension.js b/extension.js index 27383b11..a34639d8 100644 --- a/extension.js +++ b/extension.js @@ -1,5 +1,6 @@ import Clutter from 'gi://Clutter'; import GObject from 'gi://GObject'; +import Gio from 'gi://Gio'; import Meta from 'gi://Meta'; import Shell from 'gi://Shell'; import St from 'gi://St'; @@ -19,6 +20,9 @@ import { Keyboard } from './keyboard.js'; const CLIPBOARD_TYPE = St.ClipboardType.CLIPBOARD; const INDICATOR_ICON = 'edit-paste-symbolic'; +const INDICATOR_ICON_FULL = 'edit-pasted-symbolic'; + +const ext = Extension.lookupByURL(import.meta.url); let DELAYED_SELECTION_TIMEOUT = 750; let MAX_REGISTRY_LENGTH = 15; @@ -42,11 +46,6 @@ let PASTE_BUTTON = true; let PINNED_ON_BOTTOM = false; let CACHE_IMAGES = true; let EXCLUDED_APPS = []; -let CLEAR_HISTORY_ON_INTERVAL = false; -let CLEAR_HISTORY_INTERVAL = 60; -let NEXT_HISTORY_CLEAR = -1; -let CASE_SENSITIVE_SEARCH = false; -let REGEX_SEARCH = false; export default class ClipboardIndicatorExtension extends Extension { enable () { @@ -133,19 +132,34 @@ const ClipboardIndicator = GObject.registerClass({ this._buildMenu().then(() => { this._updateTopbarLayout(); this._setupListener(); - this._setupHistoryIntervalClearing(); }); } - #updateIndicatorContent(entry) { - if (this.preventIndicatorUpdate || (TOPBAR_DISPLAY_MODE !== 1 && TOPBAR_DISPLAY_MODE !== 2)) { - return; - } + _getExtIcon(iconName) { + const ext = Extension.lookupByURL(import.meta.url); + const file = ext.dir.resolve_relative_path(`icons/${iconName}.svg`); + return new Gio.FileIcon({ file }); +} + +#updateIndicatorContent(entry) { + if (this.preventIndicatorUpdate || !this.icon) { + return; + } - if (!entry || PRIVATEMODE) { + const showTopbarContent = (TOPBAR_DISPLAY_MODE === 1 || TOPBAR_DISPLAY_MODE === 2); + + if (!entry || PRIVATEMODE) { + if (showTopbarContent) { this._buttonImgPreview.destroy_all_children(); - this._buttonText.set_text("...") - } else { + this._buttonText.set_text("..."); + } + + this.icon.set_gicon(null); + this.icon.set_icon_name(INDICATOR_ICON); + } else { + this.icon.set_icon_name(null); + this.icon.set_gicon(this._getExtIcon(INDICATOR_ICON_FULL)); + if (entry.isText()) { this._buttonText.set_text(this._truncate(entry.getStringValue(), MAX_TOPBAR_LENGTH)); this._buttonImgPreview.destroy_all_children(); @@ -182,7 +196,7 @@ const ClipboardIndicator = GObject.registerClass({ }); that.searchEntry = new St.Entry({ name: 'searchEntry', - style_class: 'search-entry', + style_class: 'popup-menu-search-entry', can_focus: true, hint_text: _('Type here to search...'), track_hover: true, @@ -276,39 +290,6 @@ const ClipboardIndicator = GObject.registerClass({ }), 0 ); - - let timerBox = new St.BoxLayout({ - x_align: Clutter.ActorAlign.END, - x_expand: true - }); - - this.timerLabel = new St.Label({ - text: '', - style: 'font-family: monospace;', - x_align: Clutter.ActorAlign.END, - x_expand: true - }); - - this.resetTimerButton = new St.Button({ - style_class: 'ci-action-btn', - can_focus: true, - child: new St.Icon({ - icon_name: 'view-refresh-symbolic', - style_class: 'system-status-icon', - icon_size: 14 - }), - x_align: Clutter.ActorAlign.END, - y_align: Clutter.ActorAlign.CENTER, - }); - - this.resetTimerButton.connect('clicked', () => { - this._scheduleNextHistoryClear(); - }); - - timerBox.add_child(this.timerLabel); - timerBox.add_child(this.resetTimerButton); - this.clearMenuItem.add_child(timerBox); - this.clearMenuItem.connect('activate', that._removeAll.bind(that)); // Add 'Settings' menu item to open settings @@ -403,10 +384,7 @@ const ClipboardIndicator = GObject.registerClass({ items. It the entry is empty, the section is restored with all items set as visible. */ _onSearchTextChanged () { - - // Text to be searched converted to lowercase if search is case insensitive - let searchedText = this.searchEntry.get_text(); - if (!CASE_SENSITIVE_SEARCH) searchedText = searchedText.toLowerCase(); + let searchedText = this.searchEntry.get_text().toLowerCase(); if(searchedText === '') { this._getAllIMenuItems().forEach(function(mItem){ @@ -415,21 +393,8 @@ const ClipboardIndicator = GObject.registerClass({ } else { this._getAllIMenuItems().forEach(function(mItem){ - // Clip content converted to lowercase if search is case insensitive - let text = mItem.clipContents; - if (!CASE_SENSITIVE_SEARCH) text = text.toLowerCase(); - - let isMatching = false; - if (REGEX_SEARCH){ - /* Regex flags: - - 'm' for multiline matching (when multiline content is copied) - - 'i' for case insensitive matching when search is not set to case sensitive - */ - let text_regex = new RegExp(searchedText, 'm' + (CASE_SENSITIVE_SEARCH ? '' : 'i')); - isMatching = text_regex.test(text); - }else{ - isMatching = text.indexOf(searchedText) >= 0; - } + let text = mItem.clipContents.toLowerCase(); + let isMatching = text.indexOf(searchedText) >= 0; mItem.actor.visible = isMatching }); } @@ -633,20 +598,14 @@ const ClipboardIndicator = GObject.registerClass({ ); } - _clearHistory (invokedAutomatically = false) { + _clearHistory () { // Don't remove pinned items this.historySection._getMenuItems().forEach(mItem => { if (KEEP_SELECTED_ON_CLEAR === false || !mItem.currentlySelected) { this._removeEntry(mItem, 'delete'); } }); - - if (!invokedAutomatically) { - this._showNotification(_("Clipboard history cleared")); - } - else { - this._showNotification(_("Clipboard history cleared automatically")); - } + this._showNotification(_("Clipboard history cleared")); } _removeAll () { @@ -844,147 +803,6 @@ const ClipboardIndicator = GObject.registerClass({ }); } - _setupHistoryIntervalClearing() { - this._fetchSettings(); - - if (this._intervalSettingChangedId) { - this.extension.settings.disconnect(this._intervalSettingChangedId); - this._intervalSettingChangedId = null; - } - if (this._intervalToggleChangedId) { - this.extension.settings.disconnect(this._intervalToggleChangedId); - this._intervalToggleChangedId = null; - } - if (this._historyClearTimeoutId) { - clearTimeout(this._historyClearTimeoutId); - this._historyClearTimeoutId = null; - } - - this._intervalSettingChangedId = this.extension.settings.connect( - `changed::${PrefsFields.CLEAR_HISTORY_INTERVAL}`, - this._onHistoryIntervalClearSettingsChanged.bind(this) - ); - this._intervalToggleChangedId = this.extension.settings.connect( - `changed::${PrefsFields.CLEAR_HISTORY_ON_INTERVAL}`, - this._onHistoryIntervalClearSettingsChanged.bind(this) - ); - - - - if (!CLEAR_HISTORY_ON_INTERVAL) { - this._updateIntervalTimer(); - return; - } - - const currentTime = Math.ceil(new Date().getTime() / 1000); - - if (NEXT_HISTORY_CLEAR === -1) { //new timer - this._scheduleNextHistoryClear(); - } - else if (NEXT_HISTORY_CLEAR < currentTime) { //timer expired - this._clearHistory(true); - this._scheduleNextHistoryClear(); - } - else { //timer already set, but not expired - const timeoutMs = (NEXT_HISTORY_CLEAR - currentTime) * 1000; - this._historyClearTimeoutId = setTimeout(() => { - this._clearHistory(true); - this._scheduleNextHistoryClear(); - }, timeoutMs); - this._timerIntervalId = setInterval(() => { - this._updateIntervalTimer(); - }, 1000); - } - } - - _onHistoryIntervalClearSettingsChanged(_settings, key) { - this._fetchSettings(); - if (key === PrefsFields.CLEAR_HISTORY_INTERVAL) { - this._scheduleNextHistoryClear(); - } - else if (key === PrefsFields.CLEAR_HISTORY_ON_INTERVAL) { - if (CLEAR_HISTORY_ON_INTERVAL) { - this._resetHistoryClearTimer(); - this._setupHistoryIntervalClearing(); - } else { - this._resetHistoryClearTimer(); - } - } - } - - _scheduleNextHistoryClear() { - this._fetchSettings(); - - clearInterval(this._timerIntervalId); - if (this._historyClearTimeoutId) { - clearTimeout(this._historyClearTimeoutId); - this._historyClearTimeoutId = null; - } - - if(!CLEAR_HISTORY_ON_INTERVAL) { - this._resetHistoryClearTimer(); - return; - } - - const currentTime = Math.ceil(new Date().getTime() / 1000); - NEXT_HISTORY_CLEAR = currentTime + CLEAR_HISTORY_INTERVAL * 60; - const timeoutMs = (NEXT_HISTORY_CLEAR - currentTime) * 1000; - - this.extension.settings.set_int(PrefsFields.NEXT_HISTORY_CLEAR, NEXT_HISTORY_CLEAR); - - this._updateIntervalTimer(); - this._timerIntervalId = setInterval(() => { - this._updateIntervalTimer(); - }, 1000); - - this._historyClearTimeoutId = setTimeout(() => { - this._clearHistory(true); - this._scheduleNextHistoryClear(); - }, timeoutMs); - } - - _resetHistoryClearTimer() { - //basically just reset and stop the timer - if (this._historyClearTimeoutId) { - clearTimeout(this._historyClearTimeoutId); - this._historyClearTimeoutId = null; - } - clearInterval(this._timerIntervalId); - this._timerIntervalId = null; - this._updateIntervalTimer(); - this.extension.settings.set_int(PrefsFields.NEXT_HISTORY_CLEAR, -1); - } - - _updateIntervalTimer() { - this._fetchSettings(); - this.resetTimerButton.visible = CLEAR_HISTORY_ON_INTERVAL; - this.timerLabel.visible = CLEAR_HISTORY_ON_INTERVAL; - if (!CLEAR_HISTORY_ON_INTERVAL) return; - - - let currentTime = Math.ceil(new Date().getTime() / 1000); - let timeLeft = NEXT_HISTORY_CLEAR - currentTime; - - if (timeLeft <= 0) { - this.timerLabel.set_text(''); - return; - } - - let hours = Math.floor(timeLeft / 3600); - let minutes = Math.floor((timeLeft % 3600) / 60); - let seconds = Math.floor(timeLeft % 60); - - let formattedTime = ''; - if (hours > 0) { - formattedTime += `${hours}h `; - } - if (minutes > 0) { - formattedTime += `${minutes}m `; - } - formattedTime += `${seconds}s`; - this.timerLabel.set_text(formattedTime); - } - _openSettings () { this.extension.openSettings(); } @@ -1110,31 +928,26 @@ const ClipboardIndicator = GObject.registerClass({ _fetchSettings () { const { settings } = this.extension; - MAX_REGISTRY_LENGTH = settings.get_int(PrefsFields.HISTORY_SIZE); - MAX_ENTRY_LENGTH = settings.get_int(PrefsFields.PREVIEW_SIZE); - CACHE_ONLY_FAVORITE = settings.get_boolean(PrefsFields.CACHE_ONLY_FAVORITE); - DELETE_ENABLED = settings.get_boolean(PrefsFields.DELETE); - MOVE_ITEM_FIRST = settings.get_boolean(PrefsFields.MOVE_ITEM_FIRST); - NOTIFY_ON_COPY = settings.get_boolean(PrefsFields.NOTIFY_ON_COPY); - NOTIFY_ON_CYCLE = settings.get_boolean(PrefsFields.NOTIFY_ON_CYCLE); - CONFIRM_ON_CLEAR = settings.get_boolean(PrefsFields.CONFIRM_ON_CLEAR); - ENABLE_KEYBINDING = settings.get_boolean(PrefsFields.ENABLE_KEYBINDING); - MAX_TOPBAR_LENGTH = settings.get_int(PrefsFields.TOPBAR_PREVIEW_SIZE); - TOPBAR_DISPLAY_MODE = settings.get_int(PrefsFields.TOPBAR_DISPLAY_MODE_ID); - CLEAR_ON_BOOT = settings.get_boolean(PrefsFields.CLEAR_ON_BOOT); - PASTE_ON_SELECT = settings.get_boolean(PrefsFields.PASTE_ON_SELECT); - DISABLE_DOWN_ARROW = settings.get_boolean(PrefsFields.DISABLE_DOWN_ARROW); - STRIP_TEXT = settings.get_boolean(PrefsFields.STRIP_TEXT); - KEEP_SELECTED_ON_CLEAR = settings.get_boolean(PrefsFields.KEEP_SELECTED_ON_CLEAR); - PASTE_BUTTON = settings.get_boolean(PrefsFields.PASTE_BUTTON); - PINNED_ON_BOTTOM = settings.get_boolean(PrefsFields.PINNED_ON_BOTTOM); - CACHE_IMAGES = settings.get_boolean(PrefsFields.CACHE_IMAGES); - EXCLUDED_APPS = settings.get_strv(PrefsFields.EXCLUDED_APPS); - CLEAR_HISTORY_ON_INTERVAL = settings.get_boolean(PrefsFields.CLEAR_HISTORY_ON_INTERVAL); - CLEAR_HISTORY_INTERVAL = settings.get_int(PrefsFields.CLEAR_HISTORY_INTERVAL); - NEXT_HISTORY_CLEAR = settings.get_int(PrefsFields.NEXT_HISTORY_CLEAR); - CASE_SENSITIVE_SEARCH = settings.get_boolean(PrefsFields.CASE_SENSITIVE_SEARCH); - REGEX_SEARCH = settings.get_boolean(PrefsFields.REGEX_SEARCH); + MAX_REGISTRY_LENGTH = settings.get_int(PrefsFields.HISTORY_SIZE); + MAX_ENTRY_LENGTH = settings.get_int(PrefsFields.PREVIEW_SIZE); + CACHE_ONLY_FAVORITE = settings.get_boolean(PrefsFields.CACHE_ONLY_FAVORITE); + DELETE_ENABLED = settings.get_boolean(PrefsFields.DELETE); + MOVE_ITEM_FIRST = settings.get_boolean(PrefsFields.MOVE_ITEM_FIRST); + NOTIFY_ON_COPY = settings.get_boolean(PrefsFields.NOTIFY_ON_COPY); + NOTIFY_ON_CYCLE = settings.get_boolean(PrefsFields.NOTIFY_ON_CYCLE); + CONFIRM_ON_CLEAR = settings.get_boolean(PrefsFields.CONFIRM_ON_CLEAR); + ENABLE_KEYBINDING = settings.get_boolean(PrefsFields.ENABLE_KEYBINDING); + MAX_TOPBAR_LENGTH = settings.get_int(PrefsFields.TOPBAR_PREVIEW_SIZE); + TOPBAR_DISPLAY_MODE = settings.get_int(PrefsFields.TOPBAR_DISPLAY_MODE_ID); + CLEAR_ON_BOOT = settings.get_boolean(PrefsFields.CLEAR_ON_BOOT); + PASTE_ON_SELECT = settings.get_boolean(PrefsFields.PASTE_ON_SELECT); + DISABLE_DOWN_ARROW = settings.get_boolean(PrefsFields.DISABLE_DOWN_ARROW); + STRIP_TEXT = settings.get_boolean(PrefsFields.STRIP_TEXT); + KEEP_SELECTED_ON_CLEAR = settings.get_boolean(PrefsFields.KEEP_SELECTED_ON_CLEAR); + PASTE_BUTTON = settings.get_boolean(PrefsFields.PASTE_BUTTON); + PINNED_ON_BOTTOM = settings.get_boolean(PrefsFields.PINNED_ON_BOTTOM); + CACHE_IMAGES = settings.get_boolean(PrefsFields.CACHE_IMAGES); + EXCLUDED_APPS = settings.get_strv(PrefsFields.EXCLUDED_APPS); } async _onSettingsChange () { @@ -1235,21 +1048,6 @@ const ClipboardIndicator = GObject.registerClass({ this.extension.settings.disconnect(this._settingsChangedId); this._settingsChangedId = null; - - if (this._intervalSettingChangedId) { - this.extension.settings.disconnect(this._intervalSettingChangedId); - this._intervalSettingChangedId = null; - } - - if (this._intervalToggleChangedId) { - this.extension.settings.disconnect(this._intervalToggleChangedId); - this._intervalToggleChangedId = null; - } - - if (this._historyClearTimeoutId) { - clearTimeout(this._historyClearTimeoutId); - this._historyClearTimeoutId = null; - } } _disconnectSelectionListener () { @@ -1366,12 +1164,11 @@ const ClipboardIndicator = GObject.registerClass({ if (this._setFocusOnOpenTimeout) clearTimeout(this._setFocusOnOpenTimeout); if (this._pastingKeypressTimeout) clearTimeout(this._pastingKeypressTimeout); if (this._pastingResetTimeout) clearTimeout(this._pastingResetTimeout); - if (this._historyClearTimeoutId) clearTimeout(this._historyClearTimeoutId); - if (this._timerIntervalId) clearInterval(this._timerIntervalId); } #clearClipboard () { this.extension.clipboard.set_text(CLIPBOARD_TYPE, ""); + this.icon.icon_name = INDICATOR_ICON; this.#updateIndicatorContent(null); } diff --git a/icons/edit-pasted-symbolic.svg b/icons/edit-pasted-symbolic.svg new file mode 100644 index 00000000..26e2a2f4 --- /dev/null +++ b/icons/edit-pasted-symbolic.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/icons/edit-pasted-symbolic.svg.license b/icons/edit-pasted-symbolic.svg.license new file mode 100644 index 00000000..3a52cd55 --- /dev/null +++ b/icons/edit-pasted-symbolic.svg.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2010-2023 The GNOME Project +SPDX-FileCopyrightText: 2026 IlCraccatore2011 +SPDX-License-Identifier: LGPL-3.0-or-later diff --git a/locale/de/LC_MESSAGES/clipboard-indicator.mo b/locale/de/LC_MESSAGES/clipboard-indicator.mo index ebceb2e1b85a9810f0beeb13e8c1dc8ef47f7013..b9907b9efb62d25cf9d458451da08b6021bcc0f6 100644 GIT binary patch literal 4916 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 OK1BKxdT#%h$bSI3n||*A literal 3639 zcmZveO^h5z6~`;!kg((fVnPxKQ0BwgO)}%%jSprWWACT4vUjucZcG#;q?+!Unez5j z4b|N{**yV5C>IbBaNrU^vJl5`v49vMc_a=waNz_INFX60&L9p5iQnt#*;%h?x%W5S z_3_^OzgMsQ=f_U`KyjVnewzFH_bIguA9_E3xSl+r)FONeo`qkA55n(4{;41G=Lz^0 z`~bWK?}xvJQ*a2U;qTzX@Zazw@O~ye1)qSA!8v#mu0YxURrq0O;3F`?hv3&CTd42A zXW;js*nI~+3Gcwq!S~<;PzJ@12cg(M2|o=_!P9UNiXUAlcD@0{&YMv5cnf|Kz5`Fe zKS1&CKk$R_1V%msAAz#Z9F%yjK#B7!P{Tfa6ut>rs(u1L27dfVL^sKWTv9u7 zNo?ZZC%7eE(NvDuGe1g{YQ7(N)V> zvpV=}U-zB&ww}g&pV&+-XV%2gU)k+q{d2Nu*X2dPReaC)38bf^ZG}+O6YF7~#$GH3 zUl84qH{&f5v1QXEBQDpr*Dvo{#jEQT>Z!i}D?ZgoK=;@868MP7i zY$dCBR-j`M`g*j{Xm^LPP3stn@bzM3OKpb2wOwKg#&fk<_F9+@ZJl&YH24&A?W7j^ zx`p~%nMv51?duHX_{~buT;8?%ab_6*h03#nDt zY)^tn!@z43V;CH@Tn`Sqluq6kFH8)jClSQL(yx|OVot{M!o@rR2W=O7Rs0gg-Ktkp{1J&{N)r0}vd!SG^OCC_QFY)b^p@4gY}F3+q(k4v z(e{b)>?NZ(9=vU;ozUr!Mv<;(eaUlQWZv2Dk9Lq`=9FU6Z8n?gm38$cIfn95%pXlm z3^zzcV?AwLCuc;wpr@|cK6TjG$U81=ER~(Su@iW`^>X7H`H@MD71UqQ^Rx438?$E{ z^DpSRa|?5`&&|%y&Q4vW8#H#J@p)!ASPObHAe7fHS>HkG7k%~Et)4D*dd6kVB=o+> zI^QWx$2NAX=`H9L%R!)zyoeBfV2xDZr5mIr3+a9{wUY%4G>fT?^^Mg@40Fxdsrr~U zM3sdq-)H)nasGT&q`56Fyt1>_I5(LmF1KxLtokIRBz-}jYq?@-D=VYP8f&ZoGV z(bjpZx2f~2XlKKl(#8(TUez3TDL|RmW*NV_&L5O&J5VI4v--7xLmDex1HmYsxSKh& z>d+8uq?&9@t-q+qscddNEz_k|4rD}2M^oYVj(ny3Zs@g~uMQRpj7JIho?%H($F>-{9EZ1t zKcdADWE~R?VZYZ3nYwHT!<$_Z?=Y>EGGqPjS|p+!YJE9ce?X=REA83!3@3VGiAFB{ zI_03bQL7$TjhCF2DwY7yD0Ny!+h*QX){gEFR#Kd??Vx*o&, 2015. +# German translations for PACKAGE package. +# Copyright (C) 2026 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Il Craccatore 2011, 2026. # msgid "" msgstr "" -"Project-Id-Version: \n" +"Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-09-21 16:41+0300\n" -"PO-Revision-Date: 2024-04-23 18:10+0200\n" -"Last-Translator: Norman Henges \n" -"Language-Team: Deutsch \n" +"POT-Creation-Date: 2026-01-23 20:53+0100\n" +"PO-Revision-Date: 2026-01-24 15:31+0100\n" +"Last-Translator: IlCraccatore2011\n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 3.4.2\n" -#: extension.js:108 +#: extension.js:113 msgid "Text will be here" -msgstr "Text wird hier angezeigt" +msgstr "Der Text wird hier angezeigt" -#: extension.js:181 +#: extension.js:187 msgid "Type here to search..." -msgstr "Text eingeben, um zu suchen…" +msgstr "Hier tippen, um zu suchen..." -#: extension.js:250 prefs.js:220 +#: extension.js:256 prefs.js:257 msgid "Private mode" msgstr "Privater Modus" -#: extension.js:264 prefs.js:222 +#: extension.js:270 prefs.js:259 msgid "Clear history" msgstr "Verlauf löschen" -#: extension.js:276 +#: extension.js:315 msgid "Settings" msgstr "Einstellungen" -#: extension.js:299 +#: extension.js:338 msgid "Clipboard is empty" -msgstr "Zwischenablage ist leer" +msgstr "Die Zwischenablage ist leer" -#: extension.js:571 +#: extension.js:626 msgid "Clear all?" msgstr "Alles löschen?" -#: extension.js:572 +#: extension.js:627 msgid "Are you sure you want to delete all clipboard items?" -msgstr "Soll die Zwischenablage wirklich geleert werden?" +msgstr "Möchten Sie wirklich alle Einträge der Zwischenablage löschen?" -#: extension.js:573 +#: extension.js:628 msgid "This operation cannot be undone." -msgstr "Diese Aktion kann nicht rückgängig gemacht werden." +msgstr "Dieser Vorgang kann nicht rückgängig gemacht werden." -#: extension.js:575 +#: extension.js:630 msgid "Clear" msgstr "Löschen" -#: extension.js:575 extension.js:740 +#: extension.js:630 extension.js:801 msgid "Cancel" msgstr "Abbrechen" -#: extension.js:588 +#: extension.js:645 msgid "Clipboard history cleared" -msgstr "Verlauf der Zwischenablage gelöscht" +msgstr "Der Verlauf der Zwischenablage wurde gelöscht" -#: extension.js:739 +#: extension.js:648 +msgid "Clipboard history cleared automatically" +msgstr "Der Verlauf der Zwischenablage wurde automatisch gelöscht" + +#: extension.js:800 msgid "Copied to clipboard" -msgstr "In Zwischenablage kopiert" +msgstr "In die Zwischenablage kopiert" -#: prefs.js:30 +#: prefs.js:31 msgid "History Size" -msgstr "Größe des Verlaufs" +msgstr "Verlaufsgröße" -#: prefs.js:39 +#: prefs.js:40 msgid "Preview Size (characters)" msgstr "Vorschaugröße (Zeichen)" -#: prefs.js:48 +#: prefs.js:49 msgid "Max cache file size (MB)" -msgstr "Maximale Größe der Cachedatei (MB)" +msgstr "Maximale Cache-Dateigröße (MB)" -#: prefs.js:57 +#: prefs.js:58 msgid "Number of characters in top bar" -msgstr "Anzahl der Zeichen in oberer Leiste" +msgstr "Anzahl der Zeichen in der oberen Leiste" -#: prefs.js:66 +#: prefs.js:67 msgid "What to show in top bar" -msgstr "Folgendes in oberer Leiste anzeigen" +msgstr "In der oberen Leiste anzeigen" -#: prefs.js:71 +#: prefs.js:72 msgid "Remove down arrow in top bar" -msgstr "Pfeil in oberer Leiste ausblenden" +msgstr "Pfeil nach unten in der oberen Leiste entfernen" -#: prefs.js:75 +#: prefs.js:76 msgid "Cache only pinned items" -msgstr "Nur Favoriten im Cache behalten" +msgstr "Nur angeheftete Elemente zwischenspeichern" -#: prefs.js:79 +#: prefs.js:80 msgid "Show notification on copy" msgstr "Benachrichtigung beim Kopieren anzeigen" -#: prefs.js:83 -#, fuzzy +#: prefs.js:84 msgid "Show notification on cycle" -msgstr "Benachrichtigung beim Kopieren anzeigen" +msgstr "Benachrichtigung beim Durchblättern anzeigen" -#: prefs.js:87 +#: prefs.js:88 msgid "Show confirmation on Clear History" msgstr "Bestätigung beim Löschen des Verlaufs anzeigen" -#: prefs.js:91 +#: prefs.js:92 msgid "Remove whitespace around text" -msgstr "Leerzeichen vor und nach dem Text entfernen" +msgstr "Leerzeichen um den Text entfernen" -#: prefs.js:95 +#: prefs.js:96 msgid "Move item to the top after selection" -msgstr "Eintrag nach Auswahl nach oben verschieben" +msgstr "Element nach der Auswahl nach oben verschieben" -#: prefs.js:99 +#: prefs.js:100 msgid "Keep selected entry after Clear History" -msgstr "Gewählten Eintrag beim Löschen des Verlaufs beibehalten" +msgstr "Ausgewählten Eintrag nach dem Löschen des Verlaufs beibehalten" -#: prefs.js:103 +#: prefs.js:104 msgid "Show paste buttons" -msgstr "Schaltflächen zum Einfügen anzeigen" +msgstr "Einfügeschaltflächen anzeigen" -#: prefs.js:104 +#: prefs.js:105 msgid "Adds a paste button to each entry that lets you paste it directly" -msgstr "Fügt jedem Eintrag eine Schaltfläche zum direkten Einfügen hinzu" +msgstr "Fügt jedem Eintrag eine Einfügeschaltfläche hinzu, mit der er direkt eingefügt werden kann" -#: prefs.js:108 +#: prefs.js:109 msgid "Place the pinned section on the bottom" -msgstr "Favoriten am Ende der Liste anzeigen" +msgstr "Angehefteten Bereich unten platzieren" -#: prefs.js:109 +#: prefs.js:110 msgid "Requires restarting the extension" -msgstr "Erfordert Neustart der Erweiterung" +msgstr "Erfordert einen Neustart der Erweiterung" -#: prefs.js:113 +#: prefs.js:114 msgid "Clear clipboard history on system reboot" -msgstr "Verlauf beim Neustart des Systems löschen" +msgstr "Verlauf der Zwischenablage beim Systemneustart löschen" -#: prefs.js:117 +#: prefs.js:118 msgid "Paste on select" -msgstr "" +msgstr "Beim Auswählen einfügen" -#: prefs.js:121 +#: prefs.js:122 msgid "Cache images" -msgstr "" +msgstr "Bilder im Verlauf speichern" -#: prefs.js:126 +#: prefs.js:127 msgid "Excluded Apps" -msgstr "" +msgstr "Ausgeschlossene Anwendungen" -#: prefs.js:127 +#: prefs.js:128 msgid "Content copied will not be saved while these apps are in focus" -msgstr "" +msgstr "Kopierte Inhalte werden nicht gespeichert, solange diese Anwendungen aktiv sind" + +#: prefs.js:139 +msgid "Case-sensitive search" +msgstr "Groß-/Kleinschreibung beachten" + +#: prefs.js:143 +msgid "Regular expression matching in search" +msgstr "Suche mit regulären Ausdrücken" + +#: prefs.js:156 +msgid "Clear clipboard history on interval" +msgstr "Verlauf der Zwischenablage in regelmäßigen Abständen löschen" -#: prefs.js:146 +#: prefs.js:160 +msgid "History clear interval (in minutes)" +msgstr "Intervall zum Löschen des Verlaufs (in Minuten)" + +#: prefs.js:172 msgid "UI" -msgstr "Oberfläche" +msgstr "Benutzeroberfläche" -#: prefs.js:147 +#: prefs.js:173 msgid "Behavior" msgstr "Verhalten" -#: prefs.js:148 +#: prefs.js:174 msgid "Exclusion" -msgstr "" +msgstr "Ausschluss" -#: prefs.js:149 +#: prefs.js:175 msgid "Limits" -msgstr "Grenzwerte" +msgstr "Grenzen" -#: prefs.js:150 +#: prefs.js:176 msgid "Topbar" msgstr "Obere Leiste" -#: prefs.js:151 +#: prefs.js:177 msgid "Notifications" msgstr "Benachrichtigungen" -#: prefs.js:152 +#: prefs.js:178 msgid "Shortcuts" -msgstr "Tastenkombinationen" +msgstr "Tastenkürzel" -#: prefs.js:207 +#: prefs.js:179 +msgid "Search" +msgstr "Suche" + +#: prefs.js:244 msgid "Icon" msgstr "Symbol" -#: prefs.js:208 +#: prefs.js:245 msgid "Clipboard Content" -msgstr "Inhalt der Zwischenablage" +msgstr "Zwischenablageinhalt" -#: prefs.js:209 +#: prefs.js:246 msgid "Both" -msgstr "Beides" +msgstr "Beide" -#: prefs.js:210 +#: prefs.js:247 msgid "Neither" -msgstr "Nichts" +msgstr "Keines" -#: prefs.js:221 +#: prefs.js:258 msgid "Toggle the menu" -msgstr "Menü öffnen/schließen" +msgstr "Menü ein-/ausblenden" -#: prefs.js:223 +#: prefs.js:260 msgid "Previous entry" msgstr "Vorheriger Eintrag" -#: prefs.js:224 +#: prefs.js:261 msgid "Next entry" msgstr "Nächster Eintrag" -#: prefs.js:229 +#: prefs.js:266 msgid "Enable shortcuts" -msgstr "Tastenkombinationen aktivieren" +msgstr "Tastenkürzel aktivieren" -#: prefs.js:254 +#: prefs.js:291 msgid "Disabled" msgstr "Deaktiviert" -#: prefs.js:263 +#: prefs.js:300 msgid "Enter shortcut" -msgstr "Tastenkombination eingeben" +msgstr "Tastenkürzel eingeben" -#: prefs.js:345 +#: prefs.js:387 msgid "Window class name, e.g. \"KeePassXC\"" -msgstr "" +msgstr "Fensterklassenname, z. B. \"KeePassXC\"" -#~ msgid "Refresh Interval (ms)" -#~ msgstr "Aktualisierungsintervall (ms)" +#: prefs.js:399 +msgid "Choose from installed applications" +msgstr "Aus installierten Anwendungen auswählen" -#~ msgid "Enable Deletion" -#~ msgstr "Löschen ermöglichen" +#: prefs.js:416 +msgid "Search applications..." +msgstr "Anwendungen suchen..." diff --git a/locale/es/LC_MESSAGES/clipboard-indicator.mo b/locale/es/LC_MESSAGES/clipboard-indicator.mo index 8bd949c988a449fffd81995e24116a748a38edee..70f43c329beb273f666fd2c2c30c555ce2e04bab 100644 GIT binary patch literal 4906 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 QU@~)1_v+f7-u?aYAN@ylhX4Qo literal 3488 zcmZveO>7-k6~}L(5bBnemX`8aZun?y$T-hwDTzax*oi|7eW{6^0uo5vH*?>6*P1tX zGIz#~BUA~s8xT8Mu`V0bMTANgNL_&FTL2P*P1RLZc5L}rv4h{aGk$hjMtT0t*F7Kq zbLQN<^U&QdD~{v*KF#lkcPVuSzWqM_aQyN8O0B>@!6)IpcPqs|b)G+u!Zr9o_AA-MvABAtg55c$KN8sP!{qUdgKKNhAKXo6*kHH6__&W_h z4$s3c!0$rw`#coCKZc)$KZlRQ-$3#I4ix+Ufnxs%#w72D;b-6yJPMzM64!Nj54;IK z2VaC@=a*3O_&t>T-h>+d9X2B$Dm016)3u$f;(uv0sCD40cV%sBRC^8 z^*yNJ07@J$KuoKjLW%!pQ1to*{3N^$CBDBv$>(3){reF`^jLtOf?tL5{w90`7Et`X z2v^`wAR(zgL($_+D0Tlglsu2!+xkBZzr^)rs9}H$@Fgg5{T7P8e}yOE+wcT@fSVeg zgObM&pxE`W2Y&=V4}T4%zHdR%`ycR&@E($pbtgJL4JF?`lz!TVN8$7EE+{dIy)W~V ze%L=A=0x-q4aASEmAIvLgc>!WFZ!gt6e7rk9*6}eY`l*#H3)<+?gxcysQ`f%Gwb$09 zV{MDN+SB#e)H=6y(0jh=UUaq2TxHWb-%~4PA8?{{nzNg}I} zr}m|84Yk%C&aT)Sb?ZZ2@wFSe)YRM$>Pj;i;L;CuIyRLdmMUl`6)p7uF*kCPViL2R z;DLBqNfF}Rfu{i`YNN6{&hEza^!$OTh4zVW5K(%AOSfaF@x*6VU9}SlKJ&YUHdWjCPXsp*2EQ=6y8^JS8Yk7CjBsSS+d%ULbBzT_1wm@$yEv|){={q zX<47_EuBhwPb5n{-8;Sf!GtX^6;Gxwe3>$Z>@N@^S4W=yY>i@n9C=G4mcHIqp>HJv}bT`;qBX-B4F zB&H@avCZLeeZ8T~^04Cj?Ow{6ad8S7ieFpx#_!fbJ$;3WkD5y6UtC3J(@Qw0D(u!i zL&%)p^p=c5nUY$nXG&8tKA4RwlYtNIaAZN4-o}qCh@(?88*_t=Zc5T^tI;yv+mq^ zXVq%wa7x=t2{YzRGkrDs%H5gDk3|<5 zb8gVNV0w{F9MaKsm7^LtNesb_=mP1H{dp~~;PcA#^@(kJCG7?_cF>V*m?RZT8+y+9 zN^QEjA#@%vv{NVD+V%or3=nJ1S- z6e~I!vYn01ey-v~Qk;dxc`E?j!M0Y2urk{ng(ZbSPJEuNn;qC@zG7`2zj)uyd-*CV z>k1beRm#^+d%~4w9ikaqm_)Bu-qS68ACR`<)8$L;_JB?l=6Cz8-BIPk3-)(}*#fo% z+JeouHOerM>PWJ28c6rcK1DJm|DQ;0Mem#I&GeSyi#P2S(Hbkzu_0fIBndOUQBbYq z2rc?BD5kF~8L5XVyB5;eM$sah63+%rpkT6T@LAYuXl_a{*C;9VqC(SG*>l@q|8KMW E58b`e5C8xG diff --git a/locale/es/LC_MESSAGES/clipboard-indicator.po b/locale/es/LC_MESSAGES/clipboard-indicator.po index c69caf41..6639ba05 100644 --- a/locale/es/LC_MESSAGES/clipboard-indicator.po +++ b/locale/es/LC_MESSAGES/clipboard-indicator.po @@ -1,240 +1,261 @@ -# Spanish translation for gnome-shell-extension-clipboard-indicator. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the gnome-shell-extension-clipboard-indicator package. -# Javier Junquera , 2016. +# Spanish translations for PACKAGE package. +# Copyright (C) 2026 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Il Craccatore 2011, 2026. # msgid "" msgstr "" -"Project-Id-Version: gnome-shell-extension-clipboard-indicator\n" +"Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-09-21 16:41+0300\n" -"PO-Revision-Date: 2018-03-10 09:43-0600\n" -"Last-Translator: Adolfo Jayme Barrientos \n" -"Language-Team: \n" +"POT-Creation-Date: 2026-01-23 20:53+0100\n" +"PO-Revision-Date: 2026-01-24 15:28+0100\n" +"Last-Translator: IlCraccatore2011\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 2.0.6\n" -#: extension.js:108 +#: extension.js:113 msgid "Text will be here" msgstr "El texto aparecerá aquí" -#: extension.js:181 +#: extension.js:187 msgid "Type here to search..." -msgstr "Escriba aquí para buscar..." +msgstr "Escribe aquí para buscar..." -#: extension.js:250 prefs.js:220 +#: extension.js:256 prefs.js:257 msgid "Private mode" msgstr "Modo privado" -#: extension.js:264 prefs.js:222 +#: extension.js:270 prefs.js:259 msgid "Clear history" -msgstr "Vaciar historial" +msgstr "Borrar historial" -#: extension.js:276 +#: extension.js:315 msgid "Settings" msgstr "Configuración" -#: extension.js:299 +#: extension.js:338 msgid "Clipboard is empty" msgstr "El portapapeles está vacío" -#: extension.js:571 +#: extension.js:626 msgid "Clear all?" msgstr "¿Borrar todo?" -#: extension.js:572 +#: extension.js:627 msgid "Are you sure you want to delete all clipboard items?" -msgstr "¿Está seguro de que quiere borrar todos los ítems del portapapeles?" +msgstr "¿Seguro que quieres eliminar todos los elementos del portapapeles?" -#: extension.js:573 +#: extension.js:628 msgid "This operation cannot be undone." -msgstr "Esta operación no puede ser deshecha" +msgstr "Esta operación no se puede deshacer." -#: extension.js:575 +#: extension.js:630 msgid "Clear" msgstr "Borrar" -#: extension.js:575 extension.js:740 +#: extension.js:630 extension.js:801 msgid "Cancel" msgstr "Cancelar" -#: extension.js:588 +#: extension.js:645 msgid "Clipboard history cleared" -msgstr "Historial del portapapeles vaciado" +msgstr "El historial del portapapeles se ha borrado" -#: extension.js:739 +#: extension.js:648 +msgid "Clipboard history cleared automatically" +msgstr "El historial del portapapeles se ha borrado automáticamente" + +#: extension.js:800 msgid "Copied to clipboard" -msgstr "Copiado en el portapapeles" +msgstr "Copiado al portapapeles" -#: prefs.js:30 +#: prefs.js:31 msgid "History Size" msgstr "Tamaño del historial" -#: prefs.js:39 +#: prefs.js:40 msgid "Preview Size (characters)" -msgstr "Tamaño de previsualización (caracteres)" +msgstr "Tamaño de la vista previa (caracteres)" -#: prefs.js:48 +#: prefs.js:49 msgid "Max cache file size (MB)" -msgstr "Tamaño máximo de cache (MB)" +msgstr "Tamaño máximo del archivo de caché (MB)" -#: prefs.js:57 +#: prefs.js:58 msgid "Number of characters in top bar" msgstr "Número de caracteres en la barra superior" -#: prefs.js:66 +#: prefs.js:67 msgid "What to show in top bar" msgstr "Qué mostrar en la barra superior" -#: prefs.js:71 +#: prefs.js:72 msgid "Remove down arrow in top bar" -msgstr "Quitar la flecha hacia abajo de la barra de arriba" +msgstr "Quitar la flecha hacia abajo de la barra superior" -#: prefs.js:75 +#: prefs.js:76 msgid "Cache only pinned items" -msgstr "Cachear sólo los items anclados" +msgstr "Guardar en caché solo los elementos fijados" -#: prefs.js:79 +#: prefs.js:80 msgid "Show notification on copy" -msgstr "Mostrar una notificación al copiar" +msgstr "Mostrar notificación al copiar" -#: prefs.js:83 -#, fuzzy +#: prefs.js:84 msgid "Show notification on cycle" -msgstr "Mostrar una notificación al copiar" +msgstr "Mostrar notificación al recorrer el historial" -#: prefs.js:87 +#: prefs.js:88 msgid "Show confirmation on Clear History" -msgstr "Mostrar una confirmación al borrar el historial" +msgstr "Mostrar confirmación al borrar el historial" -#: prefs.js:91 +#: prefs.js:92 msgid "Remove whitespace around text" msgstr "Eliminar espacios en blanco alrededor del texto" -#: prefs.js:95 +#: prefs.js:96 msgid "Move item to the top after selection" -msgstr "Mover el ítem hacia arriba despues de seleccionarlo" +msgstr "Mover el elemento arriba después de seleccionarlo" -#: prefs.js:99 +#: prefs.js:100 msgid "Keep selected entry after Clear History" -msgstr "Mantener la entrada seleccionada después de limpiar el historial" +msgstr "Mantener la entrada seleccionada tras borrar el historial" -#: prefs.js:103 +#: prefs.js:104 msgid "Show paste buttons" -msgstr "Mostrar botones de pegar" +msgstr "Mostrar botones de pegado" -#: prefs.js:104 +#: prefs.js:105 msgid "Adds a paste button to each entry that lets you paste it directly" -msgstr "" -"Añade un botón de pegar a cada entrada, que te permite pegarlo directamente" +msgstr "Añade un botón de pegado a cada entrada que permite pegarla directamente" -#: prefs.js:108 +#: prefs.js:109 msgid "Place the pinned section on the bottom" -msgstr "Situar la sección anclada abajo" +msgstr "Colocar la sección fijada en la parte inferior" -#: prefs.js:109 +#: prefs.js:110 msgid "Requires restarting the extension" msgstr "Requiere reiniciar la extensión" -#: prefs.js:113 -#, fuzzy +#: prefs.js:114 msgid "Clear clipboard history on system reboot" -msgstr "Historial del portapapeles vaciado" +msgstr "Borrar el historial del portapapeles al reiniciar el sistema" -#: prefs.js:117 +#: prefs.js:118 msgid "Paste on select" -msgstr "" +msgstr "Pegar al seleccionar" -#: prefs.js:121 +#: prefs.js:122 msgid "Cache images" -msgstr "" +msgstr "Guardar imágenes en el historial" -#: prefs.js:126 +#: prefs.js:127 msgid "Excluded Apps" -msgstr "" +msgstr "Aplicaciones excluidas" -#: prefs.js:127 +#: prefs.js:128 msgid "Content copied will not be saved while these apps are in focus" -msgstr "" +msgstr "El contenido copiado no se guardará mientras estas aplicaciones estén activas" + +#: prefs.js:139 +msgid "Case-sensitive search" +msgstr "Búsqueda sensible a mayúsculas y minúsculas" + +#: prefs.js:143 +msgid "Regular expression matching in search" +msgstr "Búsqueda con expresiones regulares" + +#: prefs.js:156 +msgid "Clear clipboard history on interval" +msgstr "Borrar el historial del portapapeles periódicamente" + +#: prefs.js:160 +msgid "History clear interval (in minutes)" +msgstr "Intervalo de borrado del historial (en minutos)" -#: prefs.js:146 +#: prefs.js:172 msgid "UI" -msgstr "Interfaz de Usuario" +msgstr "Interfaz" -#: prefs.js:147 +#: prefs.js:173 msgid "Behavior" -msgstr "" +msgstr "Comportamiento" -#: prefs.js:148 +#: prefs.js:174 msgid "Exclusion" -msgstr "" +msgstr "Exclusión" -#: prefs.js:149 +#: prefs.js:175 msgid "Limits" -msgstr "Limites" +msgstr "Límites" -#: prefs.js:150 +#: prefs.js:176 msgid "Topbar" msgstr "Barra superior" -#: prefs.js:151 +#: prefs.js:177 msgid "Notifications" msgstr "Notificaciones" -#: prefs.js:152 +#: prefs.js:178 msgid "Shortcuts" -msgstr "Atajos de teclado" +msgstr "Atajos" + +#: prefs.js:179 +msgid "Search" +msgstr "Búsqueda" -#: prefs.js:207 +#: prefs.js:244 msgid "Icon" msgstr "Icono" -#: prefs.js:208 +#: prefs.js:245 msgid "Clipboard Content" msgstr "Contenido del portapapeles" -#: prefs.js:209 +#: prefs.js:246 msgid "Both" msgstr "Ambos" -#: prefs.js:210 +#: prefs.js:247 msgid "Neither" -msgstr "" +msgstr "Ninguno" -#: prefs.js:221 +#: prefs.js:258 msgid "Toggle the menu" -msgstr "Alternar menú" +msgstr "Mostrar/ocultar el menú" -#: prefs.js:223 +#: prefs.js:260 msgid "Previous entry" msgstr "Entrada anterior" -#: prefs.js:224 +#: prefs.js:261 msgid "Next entry" -msgstr "Entrada siguiente" +msgstr "Siguiente entrada" -#: prefs.js:229 +#: prefs.js:266 msgid "Enable shortcuts" -msgstr "Habilitar atajos de teclado" +msgstr "Habilitar atajos" -#: prefs.js:254 +#: prefs.js:291 msgid "Disabled" -msgstr "Deshabilitado" +msgstr "Desactivado" -#: prefs.js:263 +#: prefs.js:300 msgid "Enter shortcut" -msgstr "Introduce atajo de teclado" +msgstr "Introducir atajo" -#: prefs.js:345 +#: prefs.js:387 msgid "Window class name, e.g. \"KeePassXC\"" -msgstr "" +msgstr "Nombre de la clase de ventana, p. ej. \"KeePassXC\"" -#~ msgid "Refresh Interval (ms)" -#~ msgstr "Intervalo de actualización (ms)" +#: prefs.js:399 +msgid "Choose from installed applications" +msgstr "Elegir entre las aplicaciones instaladas" -#~ msgid "Disable cache file" -#~ msgstr "Desactivar archivo de antememoria" +#: prefs.js:416 +msgid "Search applications..." +msgstr "Buscar aplicaciones..." diff --git a/locale/fr_FR/LC_MESSAGES/clipboard-indicator.mo b/locale/fr_FR/LC_MESSAGES/clipboard-indicator.mo index f8ac1d8fbd5a041239005f02ca6f30ef5480fa8e..f7f8b3d0756f79ec8ed8c72bf813a343cfc93fa1 100644 GIT binary patch delta 1938 zcmY+^TWB0r9LMpqY0_NV^rH4&C#LPDt-J1S(l)JDjWun9)yAfXN)e~q)9&c(Y@FEy zgJ48ZM2l@43MxoNK^wsb3GIt7)*x7dRuECB6!EDl_)@4Bg!uh!vmrU;%;#iw=A8fe zpa1Sy+vQE8SC`eEF|>Q>Yw0aD#th(p3%Jns*BR4;eb|L>U;}=EwRj0z@LODgS8yR- z$6N7VyaShTlcDLvCfteb#*CT=8FX-?fQxVpm*QDmg`Z&(r*IiwM+LjE-k9asiwfuo zT!Ke&F-EBQUdBc|jSBQ#WMT6$*0R2tVvyvuAu-=rC|;Q>?t0p5l~$g}1o@@LL)Q7X@( z7WfiZfxM0?)(=rf`Za1}zs!7o4Yf{! zH0k+f)O@2G7;xm30)on}m7;1}Qyt7PJ7?@LPSJDpyXo3pwPhIS7?t{MbQP}_SJQ9D zE`9%%ZdEfIt28oIX^Kuexr?sMRNGDlN_jV3RoqEeO>knB8qh8~==adK)0G8fP=%{n zQq#AMQZc*DL1Pv$(3z#_IxD5M+EhGh6msQQbY^Ng8r6f&Txq|Pu3x*g@q4uwcDRvE zMK*}MvNvp<$hu*^VD=PBrN}zR!&1@lf~f5JzCGr+gM+@8cgtQWh~h`vFQ&cI9$gGuce0>3|!RQ-?!0hvf+>kBWpd2@}{L?v;Io3Xw zw8xLT`N~JmG3)rrLM6!_k2@W9PGYB9P9`>1V|RybeolU8SDrja#^cu(ztAzePcu6T SSz!KtetqeqiJI0^iT?p7vNzrU delta 1384 zcmXxkT}YEr9LMpq)Gepe)HJiVN2~Rs&)S?b5G_(tq8Fqrgy>?;$1-%rbP!SPqKhah zsE2O6sjiBUZWb0nL^lO#1VMpa6cJqn5d?u(Vc*}}4m;21Ja6ax&;Oif?}N7*GheEU z?-)uubuD$yW6VChP{e_9uh^J9cpuyFJ1)oi5@TqZMl8o5uEHpKaX<1ihdKCh5Vv9i z19%;)jLDcO8mqZ5jn()Xci}wNU_HHQz!qGAF|5E|R7Xd#6ceaG#!!Ks$1Rvae&zv( zdVGwV@D2JH-~6JX2>+rwT*M6+@EKEyHU_Z^73dHufJ>+WZ=f50RM6GpxgxxC}p`Qa+Cx@eg*fe6@@=$oUaw zr6rq21^Nwn#QetXxPX~3jixfCf*0~0M-7y~Zaj}G@kRdr2Mlrk85^*a-urPADg$Zc zmT^)2+(j+r462{EsEm9qC;xijCl@qB<-ZqvsDKXPW;}*F@dC0f%oEgJc#izcEC)R| zk6PnJR7UDqr)KO#4SX6E*hSoiS1ZWBUd-k%%;z5{BJX6!RHD|t0hO{nxEn`Ndt@3l z;2dgG{Xi{=pXfA!Ce;1ys2TU<&kv&d8_v*R$#M@V6H1ecqP3}7XKf}DWOh(PRHe9; zS|}PwvyD;LHwI|}I9IJB|x00vQwCw>WKGtFNrkrHrv>PhBy=u_z zbCOPK>4pArXJ};7iiX;=FU$NM-`?Tjc*04!kwBdr4s3I$0wZpB#X>e&dClWmRp+v= Is&0Dz0}IcQQ2+n{ diff --git a/locale/fr_FR/LC_MESSAGES/clipboard-indicator.po b/locale/fr_FR/LC_MESSAGES/clipboard-indicator.po index 38fd0b13..d87812cc 100644 --- a/locale/fr_FR/LC_MESSAGES/clipboard-indicator.po +++ b/locale/fr_FR/LC_MESSAGES/clipboard-indicator.po @@ -1,17 +1,20 @@ +# French translations for PACKAGE package. +# Copyright (C) 2026 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Il Craccatore 2011, 2026. +# msgid "" msgstr "" -"Project-Id-Version: unnamed project\n" +"Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-05-23 17:07+0200\n" -"PO-Revision-Date: 2025-05-23 17:15+0200\n" -"Last-Translator: p-sage <>\n" -"Language-Team: French\n" +"POT-Creation-Date: 2026-01-23 20:53+0100\n" +"PO-Revision-Date: 2026-01-24 15:22+0100\n" +"Last-Translator: IlCraccatore2011\n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -"X-Generator: Poedit 3.6\n" #: extension.js:108 msgid "Text will be here" @@ -61,6 +64,10 @@ msgstr "Annuler" msgid "Clipboard history cleared" msgstr "L'historique du presse-papiers a été effacé" +#: extension.js:648 +msgid "Clipboard history cleared automatically" +msgstr "L'historique du presse-papiers a été effacé automatiquement" + #: extension.js:739 msgid "Copied to clipboard" msgstr "Copié dans le presse-papiers" @@ -157,6 +164,22 @@ msgstr "" "Le contenu copié ne sera pas enregistré tant que ces applications sont " "actives" +#: prefs.js:139 +msgid "Case-sensitive search" +msgstr "Recherche sensible à la casse" + +#: prefs.js:143 +msgid "Regular expression matching in search" +msgstr "Recherche avec expressions régulières" + +#: prefs.js:156 +msgid "Clear clipboard history on interval" +msgstr "Effacer l'historique du presse-papiers à intervalles réguliers" + +#: prefs.js:160 +msgid "History clear interval (in minutes)" +msgstr "Intervalle d'effacement de l'historique (en minutes)" + #: prefs.js:146 msgid "UI" msgstr "Interface" @@ -185,6 +208,10 @@ msgstr "Notifications" msgid "Shortcuts" msgstr "Raccourcis" +#: prefs.js:179 +msgid "Search" +msgstr "Recherche" + #: prefs.js:207 msgid "Icon" msgstr "Icône" @@ -228,3 +255,11 @@ msgstr "Entrer un raccourci" #: prefs.js:345 msgid "Window class name, e.g. \"KeePassXC\"" msgstr "Classe de fenêtre (ex. : « KeePassXC »)" + +#: prefs.js:399 +msgid "Choose from installed applications" +msgstr "Choisir parmi les applications installées" + +#: prefs.js:416 +msgid "Search applications..." +msgstr "Rechercher des applications…" diff --git a/locale/it/LC_MESSAGES/clipboard-indicator.mo b/locale/it/LC_MESSAGES/clipboard-indicator.mo index b83b301d04cf1a4c458cd425e7fe083b9c9c19ec..28c2f5ef299837a87a105b4ac6de5df928d0c56a 100644 GIT binary patch delta 2004 zcmZA1ZD<@t9LMpw^wOkBnxyrqrj6Yu_Srl0U>`LWn-*H4*e8opeAw(cVZpsYu-RU<~SFn z@+4}4udoY$LS^PpROVWU(!(XFz=lxcK86~1AF|8ld7RXqMKqL>SFuzu<_vD-`;V>0 zY{A8ZuN5Ca1>{f@C8+0KL1pMP_Twk0%>9h&-^9b}---IY2TQnbKKa)k9-HZK0(EFk zqf+$+@-e?~*^gIIzwackjBTDpJs+a(zl_9eP9SSAr*JWTj2rPh>JT=N7A>&3gZwKs zBivAG_uyKrqgM1j@-ZKAA-m>tT#a9&Qh5axXpxiW;Tq(Y89}Z10ItI-Zom&vXXFwp za}AU9(nKpzD=4E@whgtC-FP29jXG?vp(Z?oIz$&ydw&JhPhYg9B~&2WQD@}|)Oay! z;ule`^W;eyYO<7^)I7VWgQ&Mf zWf_&DV1~@&#>}B{JGD$*NmZI>i}qMWTcY%7?^SfTbTD;Rbkdc2ZQqjYz2dnIerijp zjZ#;0hpm@dKMBUn@K_wD);pBMRnJ9f%@0Fc@%-`e&;@?Y#Zj8=ZY(vssAiMHewZC> zjIwi$9qpc9ufW`+l(7C?U*IybK^alDj z_pMvo*FROCx3y5HWS_MJ`}erOCV}q-j9(!#7ad_L>(vsU`CL5>;?R0F4=rz-y@4GK zomWu`X6Ms0ZZ_yv@T0(n zp#-5*Rn*rDZai;x&LlF7S;vjhrWbQ-=l_!JR%b`s zD#b%Cv{S#f6$?Ydagw-nJdP?hIx?LWo6hcl^AbB6vn$Sv^7)hL?3q2TT8|IA?CSiN qx@R(KhV#769FaV9ZAi7q(Y5L@FkNFpxfJvSJ delta 1351 zcmXxkOGs2v9LMo9sWbVwKE_N_%j@`lOsVq_f(o^gG9iS>O_0qDF*rJr*dnC?qugYh zi;G+p5s5?)6VBD;%#Lu`BtMZItXzH;Do3RoD=*A)BWk$FZ;3&3Y1TDOU zWyYk;5`#TVJVGz7;$hsta;#!CZPS+YyujV~5_jMSRLVE70e_&M@>Q@~Kko-P)e*dj zI@l_5i&;nQ^AS_};&%oFY03+?Zm2=s(1?Bvqc)Ci&(B~5?-x)D-^XEmvOT|v%1i|p zmC%Q}Ka6~2P9a;G(IWC+!yv*0*)&W0DV7Q51GE4ZOVui z4bhcNH3h-anO}f1r0go?VY;6FE;_NBloHuSqV~NC=9l4yGOeT^S<71AiB$}6*Ovg)dU7nHn#B}mX%vmX&O&={g b?{ZeWwcgmorFhcz4aH)o$EK!62Ylu~U2%hv diff --git a/locale/it/LC_MESSAGES/clipboard-indicator.po b/locale/it/LC_MESSAGES/clipboard-indicator.po index b9efd1f1..0271a1fc 100644 --- a/locale/it/LC_MESSAGES/clipboard-indicator.po +++ b/locale/it/LC_MESSAGES/clipboard-indicator.po @@ -1,14 +1,14 @@ # Italian translations for PACKAGE package. -# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER +# Copyright (C) 2026 THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# Il Craccatore 2011, 2025. +# Il Craccatore 2011, 2026. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-05-23 11:43+0300\n" -"PO-Revision-Date: 2025-05-23 11:43+0300\n" +"POT-Creation-Date: 2026-01-23 21:05+0100\n" +"PO-Revision-Date: 2026-01-23 21:05+0100\n" "Last-Translator: Il Craccatore 2011\n" "Language-Team: none\n" "Language: it\n" @@ -17,214 +17,247 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: extension.js:108 +#: extension.js:113 msgid "Text will be here" msgstr "Il testo sarà qui" -#: extension.js:181 +#: extension.js:187 msgid "Type here to search..." msgstr "Digita qui per cercare..." -#: extension.js:250 prefs.js:220 +#: extension.js:256 prefs.js:257 msgid "Private mode" msgstr "Modalità privata" -#: extension.js:264 prefs.js:222 +#: extension.js:270 prefs.js:259 msgid "Clear history" -msgstr "Cancella la cronologia" +msgstr "Cancella cronologia" -#: extension.js:276 +#: extension.js:315 msgid "Settings" msgstr "Impostazioni" -#: extension.js:299 +#: extension.js:338 msgid "Clipboard is empty" msgstr "Appunti vuoti" -#: extension.js:571 +#: extension.js:626 msgid "Clear all?" msgstr "Cancellare tutto?" -#: extension.js:572 +#: extension.js:627 msgid "Are you sure you want to delete all clipboard items?" -msgstr "Sei sicuro di voler eliminare tutti gli elementi negli appunti?" +msgstr "Sei sicuro di voler eliminare tutti gli elementi degli appunti?" -#: extension.js:573 +#: extension.js:628 msgid "This operation cannot be undone." msgstr "Questa operazione non può essere annullata." -#: extension.js:575 +#: extension.js:630 msgid "Clear" msgstr "Cancella" -#: extension.js:575 extension.js:740 +#: extension.js:630 extension.js:801 msgid "Cancel" msgstr "Annulla" -#: extension.js:588 +#: extension.js:645 msgid "Clipboard history cleared" msgstr "Cronologia cancellata" -#: extension.js:739 +#: extension.js:648 +msgid "Clipboard history cleared automatically" +msgstr "Cronologia cancellata automaticamente" + +#: extension.js:800 msgid "Copied to clipboard" msgstr "Copiato negli appunti" -#: prefs.js:30 +#: prefs.js:31 msgid "History Size" -msgstr "Dimensioni della cronologia" +msgstr "Dimensione della cronologia" -#: prefs.js:39 +#: prefs.js:40 msgid "Preview Size (characters)" msgstr "Dimensione anteprima (caratteri)" -#: prefs.js:48 +#: prefs.js:49 msgid "Max cache file size (MB)" -msgstr "Dimensione massima della cache (MB)" +msgstr "Dimensione massima file cache (MB)" -#: prefs.js:57 +#: prefs.js:58 msgid "Number of characters in top bar" msgstr "Numero di caratteri nella barra superiore" -#: prefs.js:66 +#: prefs.js:67 msgid "What to show in top bar" msgstr "Cosa mostrare nella barra superiore" -#: prefs.js:71 +#: prefs.js:72 msgid "Remove down arrow in top bar" -msgstr "Rimuove la freccia verso il basso nella barra superiore" +msgstr "Rimuovi la freccia verso il basso nella barra superiore" -#: prefs.js:75 +#: prefs.js:76 msgid "Cache only pinned items" msgstr "Memorizza solo gli elementi fissati" -#: prefs.js:79 +#: prefs.js:80 msgid "Show notification on copy" msgstr "Mostra notifica quando si copia" -#: prefs.js:83 +#: prefs.js:84 msgid "Show notification on cycle" msgstr "Mostra notifica per voce già copiata" -#: prefs.js:87 +#: prefs.js:88 msgid "Show confirmation on Clear History" msgstr "Mostra conferma per cancellare la cronologia" -#: prefs.js:91 +#: prefs.js:92 msgid "Remove whitespace around text" msgstr "Rimuovi spazi bianchi attorno al testo" -#: prefs.js:95 +#: prefs.js:96 msgid "Move item to the top after selection" msgstr "Sposta l'elemento in cima dopo la selezione" -#: prefs.js:99 +#: prefs.js:100 msgid "Keep selected entry after Clear History" msgstr "Mantieni l'elemento selezionato dopo aver cancellato la cronologia" -#: prefs.js:103 +#: prefs.js:104 msgid "Show paste buttons" msgstr "Mostra i pulsanti di incolla" -#: prefs.js:104 +#: prefs.js:105 msgid "Adds a paste button to each entry that lets you paste it directly" msgstr "Aggiunge a ogni voce un pulsante Incolla per copiarla direttamente" -#: prefs.js:108 +#: prefs.js:109 msgid "Place the pinned section on the bottom" msgstr "Posiziona la sezione fissata in fondo" -#: prefs.js:109 +#: prefs.js:110 msgid "Requires restarting the extension" msgstr "Richiede il riavvio dell'estensione" -#: prefs.js:113 +#: prefs.js:114 msgid "Clear clipboard history on system reboot" msgstr "Cancella la cronologia al riavvio del sistema" -#: prefs.js:117 +#: prefs.js:118 msgid "Paste on select" msgstr "Incolla alla selezione" -#: prefs.js:121 +#: prefs.js:122 msgid "Cache images" msgstr "Copia le immagini" -#: prefs.js:126 +#: prefs.js:127 msgid "Excluded Apps" msgstr "App escluse" -#: prefs.js:127 +#: prefs.js:128 msgid "Content copied will not be saved while these apps are in focus" -msgstr "Il contenuto copiato non verrà salvato mentre queste app sono in primo piano" +msgstr "Il contenuto copiato non verrà salvato mentre queste applicazioni sono in primo piano" + +#: prefs.js:139 +msgid "Case-sensitive search" +msgstr "Ricerca con distinzione tra maiuscole e minuscole" -#: prefs.js:146 +#: prefs.js:143 +msgid "Regular expression matching in search" +msgstr "Corrispondenza con espressioni regolari nella ricerca" + +#: prefs.js:156 +msgid "Clear clipboard history on interval" +msgstr "Cancella la cronologia degli appunti a intervalli" + +#: prefs.js:160 +msgid "History clear interval (in minutes)" +msgstr "Intervallo di cancellazione della cronologia (in minuti)" + +#: prefs.js:172 msgid "UI" msgstr "Interfaccia" -#: prefs.js:147 +#: prefs.js:173 msgid "Behavior" msgstr "Comportamento" -#: prefs.js:148 +#: prefs.js:174 msgid "Exclusion" msgstr "Esclusione" -#: prefs.js:149 +#: prefs.js:175 msgid "Limits" msgstr "Limiti" -#: prefs.js:150 +#: prefs.js:176 msgid "Topbar" msgstr "Barra superiore" -#: prefs.js:151 +#: prefs.js:177 msgid "Notifications" msgstr "Notifiche" -#: prefs.js:152 +#: prefs.js:178 msgid "Shortcuts" msgstr "Scorciatoie" -#: prefs.js:207 +#: prefs.js:179 +msgid "Search" +msgstr "Ricerca" + +#: prefs.js:244 msgid "Icon" msgstr "Icona" -#: prefs.js:208 +#: prefs.js:245 msgid "Clipboard Content" msgstr "Contenuto appunti" -#: prefs.js:209 +#: prefs.js:246 msgid "Both" msgstr "Entrambi" -#: prefs.js:210 +#: prefs.js:247 msgid "Neither" msgstr "Nessuno" -#: prefs.js:221 +#: prefs.js:258 msgid "Toggle the menu" msgstr "Mostra/nascondi il menu" -#: prefs.js:223 +#: prefs.js:260 msgid "Previous entry" msgstr "Voce precedente" -#: prefs.js:224 +#: prefs.js:261 msgid "Next entry" msgstr "Voce successiva" -#: prefs.js:229 +#: prefs.js:266 msgid "Enable shortcuts" msgstr "Abilita scorciatoie" -#: prefs.js:254 +#: prefs.js:291 msgid "Disabled" msgstr "Disabilitato" -#: prefs.js:263 +#: prefs.js:300 msgid "Enter shortcut" msgstr "Inserisci scorciatoia" -#: prefs.js:345 +#: prefs.js:387 msgid "Window class name, e.g. \"KeePassXC\"" -msgstr "Nome classe della finestra, ad esempio \"KeePassXC\"" +msgstr "Nome della classe della finestra, ad es. \"KeePassXC\"" + +#: prefs.js:399 +msgid "Choose from installed applications" +msgstr "Scegli dalle applicazioni installate" + +#: prefs.js:416 +msgid "Search applications..." +msgstr "Cerca applicazioni..." + diff --git a/locale/ru/LC_MESSAGES/clipboard-indicator.mo b/locale/ru/LC_MESSAGES/clipboard-indicator.mo index 9af9b83f37a43110e6940cfac52bc1ffb2d5e201..0b215f4b09313922bbcbe0f824a6a2644816093d 100644 GIT binary patch literal 6174 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 delta 2341 zcmai!S!`5Q7{?D4TD#DVE(o+7DoBw|nPDhkQxIGrSWJq78Y3oy45hTT$#iNW2~272 zLRu+sswOqIL?29i(CIQbP&x!d;zKn#*YKc;afPT4#sp(xBqsj8JH-MJ7@Wp z|M%Svdxy&-U**qzO);vNOPH7ED776n-Nl2^J6EZ#@Hkutr{UeQF;6L8RXWUq*{~Qc zhN;jG`BT+AGT;_i4r}3j7>0RDMN|hb_pmSs^Wpn&GrR;BK(Qtc3t%oRf!VMUilI#~ z9o9k#WDk_U_Q92~9`dIKcr1jk!{zV;n1+9Kg%^qNODG0^fJ@gGi$GLJeD>1aJ}( zi#h{~;JYvdCZI@u37(;BU%)PYA10eaP-Q0VHbS|76iR?yP)hj@jEJP4^CJ8SibQ|F z4R8VJi4wK25;j6vAA%Zw2H%9gLLVGJK1t*f6h~L!{qQ6oa=-V&`BugqVN&;F)W8!WU&WU!S%2YN+~{q63|unFib&a z58RfA{x!UWS&+y_p&Yyn>)>~A2lTVK4<3PD_zl!Bj}8?_YoXM%0ZL|_P|l4&v1?&9 z{2o%Nzeldre<*Dvq%LKOVthnu z5mOpahNyRQh++}>6lBOJBTYgNx!n%jn$P?ja6iRD)>bejL+N*u6|jBl*mZV)N*{vu0iDYSWqeg!wb|z47U^vN>i!`d)J){r7^pP|Km< z3t`WW{hp_Sq1J|$=4x#|&G1C3Jl-mguS)wiR{J-s_WHfvjGckjuxEED(A?S-2)Bf) z_2Yq1!^;8v z)8X`+(yY>_r)*T)^DLgV6HcddLfa4dagh_poc;>UTY{A_o|DdibHX`pqt1Y~V{CWK zn$i|K_M+474A=`|#-^hw~naDhV$q#MVQ?*f*UKkqd9j6@gJ8MwXCy>aIPDj>;Rgv96jRva{G_{ zG0EM{d_qz{!Rbf*NwsVG-y4EMY%m=stRlVP8ebN|;, 2017. +# Il Craccatore 2011, 2026. # -# SPDX-FileCopyrightText: 2025 Mariya Shikunova msgid "" msgstr "" -"Project-Id-Version: \n" +"Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-09-21 16:41+0300\n" -"PO-Revision-Date: 2025-05-15 18:37+0300\n" -"Last-Translator: Mariya Shikunova \n" -"Language-Team: Basealt Translation Team\n" +"POT-Creation-Date: 2026-01-23 20:53+0100\n" +"PO-Revision-Date: 2026-01-24 15:35+0100\n" +"Last-Translator: IlCraccatore2011\n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " -"|| n%100>=20) ? 1 : 2);\n" -"X-Generator: Poedit 3.6\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -#: extension.js:108 +#: extension.js:113 msgid "Text will be here" msgstr "Текст будет здесь" -#: extension.js:181 +#: extension.js:187 msgid "Type here to search..." -msgstr "Введите здесь для поиска..." +msgstr "Введите текст для поиска..." -#: extension.js:250 prefs.js:220 +#: extension.js:256 prefs.js:257 msgid "Private mode" msgstr "Приватный режим" -#: extension.js:264 prefs.js:222 +#: extension.js:270 prefs.js:259 msgid "Clear history" msgstr "Очистить историю" -#: extension.js:276 +#: extension.js:315 msgid "Settings" msgstr "Настройки" -#: extension.js:299 +#: extension.js:338 msgid "Clipboard is empty" msgstr "Буфер обмена пуст" -#: extension.js:571 +#: extension.js:626 msgid "Clear all?" msgstr "Очистить всё?" -#: extension.js:572 +#: extension.js:627 msgid "Are you sure you want to delete all clipboard items?" msgstr "Вы уверены, что хотите удалить все элементы буфера обмена?" -#: extension.js:573 +#: extension.js:628 msgid "This operation cannot be undone." msgstr "Эту операцию нельзя отменить." -#: extension.js:575 +#: extension.js:630 msgid "Clear" msgstr "Очистить" -#: extension.js:575 extension.js:740 +#: extension.js:630 extension.js:801 msgid "Cancel" -msgstr "Отменить" +msgstr "Отмена" -#: extension.js:588 +#: extension.js:645 msgid "Clipboard history cleared" msgstr "История буфера обмена очищена" -#: extension.js:739 +#: extension.js:648 +msgid "Clipboard history cleared automatically" +msgstr "История буфера обмена была очищена автоматически" + +#: extension.js:800 msgid "Copied to clipboard" msgstr "Скопировано в буфер обмена" -#: prefs.js:30 +#: prefs.js:31 msgid "History Size" msgstr "Размер истории" -#: prefs.js:39 +#: prefs.js:40 msgid "Preview Size (characters)" -msgstr "Длина строки в меню (символов)" +msgstr "Размер предпросмотра (символы)" -#: prefs.js:48 +#: prefs.js:49 msgid "Max cache file size (MB)" -msgstr "Максимальный размер кэша (мб)" +msgstr "Максимальный размер файла кэша (МБ)" -#: prefs.js:57 +#: prefs.js:58 msgid "Number of characters in top bar" msgstr "Количество символов в верхней панели" -#: prefs.js:66 +#: prefs.js:67 msgid "What to show in top bar" msgstr "Что показывать в верхней панели" -#: prefs.js:71 +#: prefs.js:72 msgid "Remove down arrow in top bar" msgstr "Убрать стрелку вниз в верхней панели" -#: prefs.js:75 +#: prefs.js:76 msgid "Cache only pinned items" -msgstr "Кэшировать только избранное" +msgstr "Кэшировать только закреплённые элементы" -#: prefs.js:79 +#: prefs.js:80 msgid "Show notification on copy" msgstr "Показывать уведомление при копировании" -#: prefs.js:83 +#: prefs.js:84 msgid "Show notification on cycle" -msgstr "Показывать уведомление при циклическом переключении" +msgstr "Показывать уведомление при переключении" -#: prefs.js:87 +#: prefs.js:88 msgid "Show confirmation on Clear History" -msgstr "Показывать уведомление при очистке истории" +msgstr "Показывать подтверждение при очистке истории" -#: prefs.js:91 +#: prefs.js:92 msgid "Remove whitespace around text" -msgstr "Удалить пробелы в тексте" +msgstr "Удалять пробелы вокруг текста" -#: prefs.js:95 +#: prefs.js:96 msgid "Move item to the top after selection" -msgstr "Переместить запись в начало после выделения" +msgstr "Перемещать элемент наверх после выбора" -#: prefs.js:99 +#: prefs.js:100 msgid "Keep selected entry after Clear History" -msgstr "Не очищать выбранную запись после очитски истории" +msgstr "Сохранять выбранный элемент после очистки истории" -#: prefs.js:103 +#: prefs.js:104 msgid "Show paste buttons" msgstr "Показывать кнопки вставки" -#: prefs.js:104 +#: prefs.js:105 msgid "Adds a paste button to each entry that lets you paste it directly" -msgstr "" -"Добавить кнопку вставки на каждую запись, позволяющая вставить напрямую" +msgstr "Добавляет кнопку вставки к каждому элементу для прямой вставки" -#: prefs.js:108 +#: prefs.js:109 msgid "Place the pinned section on the bottom" -msgstr "Переместить избранный элемент в конец истории" +msgstr "Разместить закреплённый раздел внизу" -#: prefs.js:109 +#: prefs.js:110 msgid "Requires restarting the extension" -msgstr "Требует перезагрузки расширения" +msgstr "Требуется перезапуск расширения" -#: prefs.js:113 +#: prefs.js:114 msgid "Clear clipboard history on system reboot" -msgstr "История буфера обмена очищена" +msgstr "Очищать историю буфера обмена при перезагрузке системы" -#: prefs.js:117 +#: prefs.js:118 msgid "Paste on select" -msgstr "Вставка при выделении" +msgstr "Вставлять при выборе" -#: prefs.js:121 +#: prefs.js:122 msgid "Cache images" -msgstr "Кеш изображений" +msgstr "Сохранять изображения в истории" -#: prefs.js:126 +#: prefs.js:127 msgid "Excluded Apps" msgstr "Исключённые приложения" -#: prefs.js:127 +#: prefs.js:128 msgid "Content copied will not be saved while these apps are in focus" -msgstr "" -"Скопированное содержимое не будет сохранено, пока эти приложения находятся в " -"фокусе" +msgstr "Скопированное содержимое не будет сохраняться, пока эти приложения активны" + +#: prefs.js:139 +msgid "Case-sensitive search" +msgstr "Поиск с учётом регистра" + +#: prefs.js:143 +msgid "Regular expression matching in search" +msgstr "Поиск с использованием регулярных выражений" + +#: prefs.js:156 +msgid "Clear clipboard history on interval" +msgstr "Очищать историю буфера обмена по интервалу" -#: prefs.js:146 +#: prefs.js:160 +msgid "History clear interval (in minutes)" +msgstr "Интервал очистки истории (в минутах)" + +#: prefs.js:172 msgid "UI" msgstr "Интерфейс" -#: prefs.js:147 +#: prefs.js:173 msgid "Behavior" msgstr "Поведение" -#: prefs.js:148 +#: prefs.js:174 msgid "Exclusion" -msgstr "Исключение" +msgstr "Исключения" -#: prefs.js:149 +#: prefs.js:175 msgid "Limits" msgstr "Ограничения" -#: prefs.js:150 +#: prefs.js:176 msgid "Topbar" msgstr "Верхняя панель" -#: prefs.js:151 +#: prefs.js:177 msgid "Notifications" msgstr "Уведомления" -#: prefs.js:152 +#: prefs.js:178 msgid "Shortcuts" -msgstr "Горячие клавиши" +msgstr "Сочетания клавиш" + +#: prefs.js:179 +msgid "Search" +msgstr "Поиск" -#: prefs.js:207 +#: prefs.js:244 msgid "Icon" msgstr "Значок" -#: prefs.js:208 +#: prefs.js:245 msgid "Clipboard Content" msgstr "Содержимое буфера обмена" -#: prefs.js:209 +#: prefs.js:246 msgid "Both" msgstr "Оба" -#: prefs.js:210 +#: prefs.js:247 msgid "Neither" msgstr "Ничего" -#: prefs.js:221 +#: prefs.js:258 msgid "Toggle the menu" -msgstr "Показать меню" +msgstr "Показать/скрыть меню" -#: prefs.js:223 +#: prefs.js:260 msgid "Previous entry" -msgstr "Предыдущая запись" +msgstr "Предыдущий элемент" -#: prefs.js:224 +#: prefs.js:261 msgid "Next entry" -msgstr "Следующая запись" +msgstr "Следующий элемент" -#: prefs.js:229 +#: prefs.js:266 msgid "Enable shortcuts" -msgstr "Горячие клавиши" +msgstr "Включить сочетания клавиш" -#: prefs.js:254 +#: prefs.js:291 msgid "Disabled" -msgstr "Выключено" +msgstr "Отключено" -#: prefs.js:263 +#: prefs.js:300 msgid "Enter shortcut" -msgstr "Ввести горячие клавиши" +msgstr "Введите сочетание клавиш" -#: prefs.js:345 +#: prefs.js:387 msgid "Window class name, e.g. \"KeePassXC\"" -msgstr "" +msgstr "Имя класса окна, например \"KeePassXC\"" + +#: prefs.js:399 +msgid "Choose from installed applications" +msgstr "Выбрать из установленных приложений" -#~ msgid "Refresh Interval (ms)" -#~ msgstr "Интервал обновления (мс)" +#: prefs.js:416 +msgid "Search applications..." +msgstr "Поиск приложений..." diff --git a/metadata.json b/metadata.json index e6b908b2..59cb2339 100644 --- a/metadata.json +++ b/metadata.json @@ -7,7 +7,7 @@ ], "uuid": "clipboard-indicator@tudmotu.com", "name": "Clipboard Indicator", - "description": "The most popular clipboard manager for GNOME, with over 1M downloads", + "description": "The most popular clipboard manager for GNOME, with over 2M downloads", "url": "https://github.com/Tudmotu/gnome-shell-extension-clipboard-indicator", "gettext-domain": "clipboard-indicator", "settings-schema": "org.gnome.shell.extensions.clipboard-indicator" From 381063409f5276e5300cce8faf31c8bb380120b6 Mon Sep 17 00:00:00 2001 From: IlCraccatore2011 Date: Mon, 26 Jan 2026 11:38:21 +0100 Subject: [PATCH 2/3] Updated languages, fixed search box theme bug, added new icon when there is clipboard in the system --- extension.js | 2674 +++++++++++++++++++++++++++----------------------- 1 file changed, 1447 insertions(+), 1227 deletions(-) diff --git a/extension.js b/extension.js index a34639d8..ecfbacd8 100644 --- a/extension.js +++ b/extension.js @@ -1,1227 +1,1447 @@ -import Clutter from 'gi://Clutter'; -import GObject from 'gi://GObject'; -import Gio from 'gi://Gio'; -import Meta from 'gi://Meta'; -import Shell from 'gi://Shell'; -import St from 'gi://St'; - -import * as AnimationUtils from 'resource:///org/gnome/shell/misc/animationUtils.js'; -import * as MessageTray from 'resource:///org/gnome/shell/ui/messageTray.js'; -import * as Main from 'resource:///org/gnome/shell/ui/main.js'; -import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js'; -import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; -import { Extension, gettext as _ } from 'resource:///org/gnome/shell/extensions/extension.js'; - -import { Registry, ClipboardEntry } from './registry.js'; -import { DialogManager } from './confirmDialog.js'; -import { PrefsFields } from './constants.js'; -import { Keyboard } from './keyboard.js'; - -const CLIPBOARD_TYPE = St.ClipboardType.CLIPBOARD; - -const INDICATOR_ICON = 'edit-paste-symbolic'; -const INDICATOR_ICON_FULL = 'edit-pasted-symbolic'; - -const ext = Extension.lookupByURL(import.meta.url); - -let DELAYED_SELECTION_TIMEOUT = 750; -let MAX_REGISTRY_LENGTH = 15; -let MAX_ENTRY_LENGTH = 50; -let CACHE_ONLY_FAVORITE = false; -let DELETE_ENABLED = true; -let MOVE_ITEM_FIRST = false; -let ENABLE_KEYBINDING = true; -let PRIVATEMODE = false; -let NOTIFY_ON_COPY = true; -let NOTIFY_ON_CYCLE = true; -let CONFIRM_ON_CLEAR = true; -let MAX_TOPBAR_LENGTH = 15; -let TOPBAR_DISPLAY_MODE = 1; //0 - only icon, 1 - only clipboard content, 2 - both, 3 - neither -let CLEAR_ON_BOOT = false; -let PASTE_ON_SELECT = false; -let DISABLE_DOWN_ARROW = false; -let STRIP_TEXT = false; -let KEEP_SELECTED_ON_CLEAR = false; -let PASTE_BUTTON = true; -let PINNED_ON_BOTTOM = false; -let CACHE_IMAGES = true; -let EXCLUDED_APPS = []; - -export default class ClipboardIndicatorExtension extends Extension { - enable () { - this.clipboardIndicator = new ClipboardIndicator({ - clipboard: St.Clipboard.get_default(), - settings: this.getSettings(), - openSettings: this.openPreferences, - uuid: this.uuid - }); - - Main.panel.addToStatusArea('clipboardIndicator', this.clipboardIndicator, 1); - } - - disable () { - this.clipboardIndicator.destroy(); - this.clipboardIndicator = null; - EXCLUDED_APPS = []; - } -} - -const ClipboardIndicator = GObject.registerClass({ - GTypeName: 'ClipboardIndicator' -}, class ClipboardIndicator extends PanelMenu.Button { - #refreshInProgress = false; - - destroy () { - this._disconnectSettings(); - this._unbindShortcuts(); - this._disconnectSelectionListener(); - this._clearDelayedSelectionTimeout(); - this.#clearTimeouts(); - this.dialogManager.destroy(); - this.keyboard.destroy(); - - super.destroy(); - } - - _init (extension) { - super._init(0.0, "ClipboardIndicator"); - this.extension = extension; - this.registry = new Registry(extension); - this.keyboard = new Keyboard(); - this._settingsChangedId = null; - this._selectionOwnerChangedId = null; - this._historyLabel = null; - this._buttonText = null; - this._disableDownArrow = null; - - this._shortcutsBindingIds = []; - this.clipItemsRadioGroup = []; - - let hbox = new St.BoxLayout({ - style_class: 'panel-status-menu-box clipboard-indicator-hbox' - }); - - this.hbox = hbox; - - this.icon = new St.Icon({ - icon_name: INDICATOR_ICON, - style_class: 'system-status-icon clipboard-indicator-icon' - }); - - this._buttonText = new St.Label({ - text: _('Text will be here'), - y_align: Clutter.ActorAlign.CENTER - }); - - this._buttonImgPreview = new St.Bin({ - style_class: 'clipboard-indicator-topbar-preview' - }); - - hbox.add_child(this.icon); - hbox.add_child(this._buttonText); - hbox.add_child(this._buttonImgPreview); - this._downArrow = PopupMenu.arrowIcon(St.Side.BOTTOM); - hbox.add_child(this._downArrow); - this.add_child(hbox); - this._createHistoryLabel(); - this._loadSettings(); - - if (CLEAR_ON_BOOT) this.registry.clearCacheFolder(); - - this.dialogManager = new DialogManager(); - this._buildMenu().then(() => { - this._updateTopbarLayout(); - this._setupListener(); - }); - } - - _getExtIcon(iconName) { - const ext = Extension.lookupByURL(import.meta.url); - const file = ext.dir.resolve_relative_path(`icons/${iconName}.svg`); - return new Gio.FileIcon({ file }); -} - -#updateIndicatorContent(entry) { - if (this.preventIndicatorUpdate || !this.icon) { - return; - } - - const showTopbarContent = (TOPBAR_DISPLAY_MODE === 1 || TOPBAR_DISPLAY_MODE === 2); - - if (!entry || PRIVATEMODE) { - if (showTopbarContent) { - this._buttonImgPreview.destroy_all_children(); - this._buttonText.set_text("..."); - } - - this.icon.set_gicon(null); - this.icon.set_icon_name(INDICATOR_ICON); - } else { - this.icon.set_icon_name(null); - this.icon.set_gicon(this._getExtIcon(INDICATOR_ICON_FULL)); - - if (entry.isText()) { - this._buttonText.set_text(this._truncate(entry.getStringValue(), MAX_TOPBAR_LENGTH)); - this._buttonImgPreview.destroy_all_children(); - } - else if (entry.isImage()) { - this._buttonText.set_text(''); - this._buttonImgPreview.destroy_all_children(); - this.registry.getEntryAsImage(entry).then(img => { - img.add_style_class_name('clipboard-indicator-img-preview'); - img.y_align = Clutter.ActorAlign.CENTER; - - // icon only renders properly in setTimeout for some arcane reason - this._imagePreviewTimeout = setTimeout(() => { - this._buttonImgPreview.set_child(img); - }, 0); - }); - } - } - } - - async _buildMenu () { - let that = this; - const clipHistory = await this._getCache(); - let lastIdx = clipHistory.length - 1; - let clipItemsArr = that.clipItemsRadioGroup; - - /* This create the search entry, which is add to a menuItem. - The searchEntry is connected to the function for research. - The menu itself is connected to some shitty hack in order to - grab the focus of the keyboard. */ - that._entryItem = new PopupMenu.PopupBaseMenuItem({ - reactive: false, - can_focus: false - }); - that.searchEntry = new St.Entry({ - name: 'searchEntry', - style_class: 'popup-menu-search-entry', - can_focus: true, - hint_text: _('Type here to search...'), - track_hover: true, - x_expand: true, - y_expand: true, - primary_icon: new St.Icon({ icon_name: 'edit-find-symbolic' }) - }); - - that.searchEntry.get_clutter_text().connect( - 'text-changed', - that._onSearchTextChanged.bind(that) - ); - - that._entryItem.add_child(that.searchEntry); - - that.menu.connect('open-state-changed', (self, open) => { - this._setFocusOnOpenTimeout = setTimeout(() => { - if (open) { - if (this.clipItemsRadioGroup.length > 0) { - that.searchEntry.set_text(''); - global.stage.set_key_focus(that.searchEntry); - } - else { - global.stage.set_key_focus(that.privateModeMenuItem); - } - } - }, 50); - }); - - // Create menu sections for items - // Favorites - that.favoritesSection = new PopupMenu.PopupMenuSection(); - - that.scrollViewFavoritesMenuSection = new PopupMenu.PopupMenuSection(); - this.favoritesScrollView = new St.ScrollView({ - style_class: 'ci-history-menu-section', - overlay_scrollbars: true - }); - this.favoritesScrollView.add_child(that.favoritesSection.actor); - - that.scrollViewFavoritesMenuSection.actor.add_child(this.favoritesScrollView); - this.favoritesSeparator = new PopupMenu.PopupSeparatorMenuItem(); - - // History - that.historySection = new PopupMenu.PopupMenuSection(); - - that.scrollViewMenuSection = new PopupMenu.PopupMenuSection(); - this.historyScrollView = new St.ScrollView({ - style_class: 'ci-main-menu-section ci-history-menu-section', - overlay_scrollbars: true - }); - this.historyScrollView.add_child(that.historySection.actor); - - that.scrollViewMenuSection.actor.add_child(this.historyScrollView); - - // Add separator - this.historySeparator = new PopupMenu.PopupSeparatorMenuItem(); - - // Add sections ordered according to settings - if (PINNED_ON_BOTTOM) { - that.menu.addMenuItem(that.scrollViewMenuSection); - that.menu.addMenuItem(that.scrollViewFavoritesMenuSection); - } - else { - that.menu.addMenuItem(that.scrollViewFavoritesMenuSection); - that.menu.addMenuItem(that.scrollViewMenuSection); - } - - // Private mode switch - that.privateModeMenuItem = new PopupMenu.PopupSwitchMenuItem( - _("Private mode"), PRIVATEMODE, { reactive: true }); - that.privateModeMenuItem.connect('toggled', - that._onPrivateModeSwitch.bind(that)); - that.privateModeMenuItem.insert_child_at_index( - new St.Icon({ - icon_name: 'security-medium-symbolic', - style_class: 'clipboard-menu-icon', - y_align: Clutter.ActorAlign.CENTER - }), - 0 - ); - that.menu.addMenuItem(that.privateModeMenuItem); - - // Add 'Clear' button which removes all items from cache - this.clearMenuItem = new PopupMenu.PopupMenuItem(_('Clear history')); - this.clearMenuItem.insert_child_at_index( - new St.Icon({ - icon_name: 'user-trash-symbolic', - style_class: 'clipboard-menu-icon', - y_align: Clutter.ActorAlign.CENTER - }), - 0 - ); - this.clearMenuItem.connect('activate', that._removeAll.bind(that)); - - // Add 'Settings' menu item to open settings - this.settingsMenuItem = new PopupMenu.PopupMenuItem(_('Settings')); - this.settingsMenuItem.insert_child_at_index( - new St.Icon({ - icon_name: 'preferences-system-symbolic', - style_class: 'clipboard-menu-icon', - y_align: Clutter.ActorAlign.CENTER - }), - 0 - ); - that.menu.addMenuItem(this.settingsMenuItem); - this.settingsMenuItem.connect('activate', that._openSettings.bind(that)); - - // Empty state section - this.emptyStateSection = new St.BoxLayout({ - style_class: 'clipboard-indicator-empty-state', - vertical: true - }); - this.emptyStateSection.add_child(new St.Icon({ - icon_name: INDICATOR_ICON, - style_class: 'system-status-icon clipboard-indicator-icon', - x_align: Clutter.ActorAlign.CENTER - })); - this.emptyStateSection.add_child(new St.Label({ - text: _('Clipboard is empty'), - x_align: Clutter.ActorAlign.CENTER - })); - - // Add cached items - clipHistory.forEach(entry => this._addEntry(entry)); - - if (lastIdx >= 0) { - that._selectMenuItem(clipItemsArr[lastIdx]); - } - - this.#showElements(); - } - - #hideElements() { - if (this.menu.box.contains(this._entryItem)) this.menu.box.remove_child(this._entryItem); - if (this.menu.box.contains(this.favoritesSeparator)) this.menu.box.remove_child(this.favoritesSeparator); - if (this.menu.box.contains(this.historySeparator)) this.menu.box.remove_child(this.historySeparator); - if (this.menu.box.contains(this.clearMenuItem)) this.menu.box.remove_child(this.clearMenuItem); - if (this.menu.box.contains(this.emptyStateSection)) this.menu.box.remove_child(this.emptyStateSection); - } - - #showElements() { - if (this.clipItemsRadioGroup.length > 0) { - if (this.menu.box.contains(this._entryItem) === false) { - this.menu.box.insert_child_at_index(this._entryItem, 0); - } - if (this.menu.box.contains(this.clearMenuItem) === false) { - this.menu.box.insert_child_below(this.clearMenuItem, this.settingsMenuItem); - } - if (this.menu.box.contains(this.emptyStateSection) === true) { - this.menu.box.remove_child(this.emptyStateSection); - } - - if (this.favoritesSection._getMenuItems().length > 0) { - if (this.menu.box.contains(this.favoritesSeparator) === false) { - this.menu.box.insert_child_above(this.favoritesSeparator, this.scrollViewFavoritesMenuSection.actor); - } - } - else if (this.menu.box.contains(this.favoritesSeparator) === true) { - this.menu.box.remove_child(this.favoritesSeparator); - } - - if (this.historySection._getMenuItems().length > 0) { - if (this.menu.box.contains(this.historySeparator) === false) { - this.menu.box.insert_child_above(this.historySeparator, this.scrollViewMenuSection.actor); - } - } - else if (this.menu.box.contains(this.historySeparator) === true) { - this.menu.box.remove_child(this.historySeparator); - } - } - else if (this.menu.box.contains(this.emptyStateSection) === false) { - this.#renderEmptyState(); - } - } - - #renderEmptyState () { - this.#hideElements(); - this.menu.box.insert_child_at_index(this.emptyStateSection, 0); - } - - /* When text change, this function will check, for each item of the - historySection and favoritesSestion, if it should be visible or not (based on words contained - in the clipContents attribute of the item). It doesn't destroy or create - items. It the entry is empty, the section is restored with all items - set as visible. */ - _onSearchTextChanged () { - let searchedText = this.searchEntry.get_text().toLowerCase(); - - if(searchedText === '') { - this._getAllIMenuItems().forEach(function(mItem){ - mItem.actor.visible = true; - }); - } - else { - this._getAllIMenuItems().forEach(function(mItem){ - let text = mItem.clipContents.toLowerCase(); - let isMatching = text.indexOf(searchedText) >= 0; - mItem.actor.visible = isMatching - }); - } - } - - _truncate (string, length) { - let shortened = string.replace(/\s+/g, ' '); - - let chars = [...shortened] - if (chars.length > length) - shortened = chars.slice(0, length - 1).join('') + '...'; - - return shortened; - } - - _setEntryLabel (menuItem) { - const { entry } = menuItem; - if (entry.isText()) { - menuItem.label.set_text(this._truncate(entry.getStringValue(), MAX_ENTRY_LENGTH)); - } - else if (entry.isImage()) { - this.registry.getEntryAsImage(entry).then(img => { - img.add_style_class_name('clipboard-menu-img-preview'); - if (menuItem.previewImage) { - menuItem.remove_child(menuItem.previewImage); - } - menuItem.previewImage = img; - menuItem.insert_child_below(img, menuItem.label); - }); - } - } - - _findNextMenuItem (currentMenutItem) { - let currentIndex = this.clipItemsRadioGroup.indexOf(currentMenutItem); - - // for only one item - if(this.clipItemsRadioGroup.length === 1) { - return null; - } - - // when focus is in middle of the displayed list - for (let i = currentIndex - 1; i >= 0; i--) { - let menuItem = this.clipItemsRadioGroup[i]; - if (menuItem.actor.visible) { - return menuItem; - } - } - - // when focus is at the last element of the displayed list - let beforeMenuItem = this.clipItemsRadioGroup[currentIndex + 1]; - if(beforeMenuItem.actor.visible){ - return beforeMenuItem; - } - - return null; - } - - #selectNextMenuItem (menuItem) { - let nextMenuItem = this._findNextMenuItem(menuItem); - - if (nextMenuItem) { - nextMenuItem.actor.grab_key_focus(); - } else { - this.privateModeMenuItem.actor.grab_key_focus(); - } - } - - _addEntry (entry, autoSelect, autoSetClip) { - let menuItem = new PopupMenu.PopupMenuItem(''); - - menuItem.menu = this.menu; - menuItem.entry = entry; - menuItem.clipContents = entry.getStringValue(); - menuItem.radioGroup = this.clipItemsRadioGroup; - menuItem.buttonPressId = menuItem.connect('activate', - autoSet => this._onMenuItemSelectedAndMenuClose(menuItem, autoSet)); - menuItem.connect('key-focus-in', () => { - const viewToScroll = menuItem.entry.isFavorite() ? - this.favoritesScrollView : this.historyScrollView; - AnimationUtils.ensureActorVisibleInScrollView(viewToScroll, menuItem); - }); - menuItem.actor.connect('key-press-event', (actor, event) => { - switch (event.get_key_symbol()) { - case Clutter.KEY_Delete: - this.#selectNextMenuItem(menuItem); - this._removeEntry(menuItem, 'delete'); - break; - case Clutter.KEY_p: - this.#selectNextMenuItem(menuItem); - this._favoriteToggle(menuItem); - break; - case Clutter.KEY_v: - this.#pasteItem(menuItem); - break; - case Clutter.KEY_KP_Enter: - case Clutter.KEY_Return: - if (PASTE_ON_SELECT) { - this.#pasteItem(menuItem); - } - this._onMenuItemSelectedAndMenuClose(menuItem, true); - break; - } - }) - - this._setEntryLabel(menuItem); - this.clipItemsRadioGroup.push(menuItem); - - // Favorite button - let iconfav = new St.Icon({ - icon_name: 'view-pin-symbolic', - style_class: 'system-status-icon' - }); - - let icofavBtn = new St.Button({ - style_class: 'ci-pin-btn ci-action-btn', - can_focus: true, - child: iconfav, - x_align: Clutter.ActorAlign.END, - x_expand: true, - y_expand: true - }); - - menuItem.actor.add_child(icofavBtn); - menuItem.icofavBtn = icofavBtn; - menuItem.favoritePressId = icofavBtn.connect('clicked', - () => this._favoriteToggle(menuItem) - ); - - // Paste button - menuItem.pasteBtn = new St.Button({ - style_class: 'ci-action-btn', - can_focus: true, - child: new St.Icon({ - icon_name: 'edit-paste-symbolic', - style_class: 'system-status-icon' - }), - x_align: Clutter.ActorAlign.END, - x_expand: false, - y_expand: true, - visible: PASTE_BUTTON - }); - - menuItem.pasteBtn.connect('clicked', - () => this.#pasteItem(menuItem) - ); - - menuItem.actor.add_child(menuItem.pasteBtn); - - // Delete button - let icon = new St.Icon({ - icon_name: 'edit-delete-symbolic', //'mail-attachment-symbolic', - style_class: 'system-status-icon' - }); - - let icoBtn = new St.Button({ - style_class: 'ci-action-btn', - can_focus: true, - child: icon, - x_align: Clutter.ActorAlign.END, - x_expand: false, - y_expand: true - }); - - menuItem.actor.add_child(icoBtn); - menuItem.icoBtn = icoBtn; - menuItem.deletePressId = icoBtn.connect('clicked', - () => this._removeEntry(menuItem, 'delete') - ); - - if (entry.isFavorite()) { - this.favoritesSection.addMenuItem(menuItem, 0); - } else { - this.historySection.addMenuItem(menuItem, 0); - } - - if (autoSelect === true) { - this._selectMenuItem(menuItem, autoSetClip); - } - else { - menuItem.setOrnament(PopupMenu.Ornament.NONE); - } - - this.#showElements(); - } - - _favoriteToggle (menuItem) { - menuItem.entry.favorite = menuItem.entry.isFavorite() ? false : true; - this._moveItemFirst(menuItem); - this._updateCache(); - this.#showElements(); - } - - _confirmRemoveAll () { - const title = _("Clear all?"); - const message = _("Are you sure you want to delete all clipboard items?"); - const sub_message = _("This operation cannot be undone."); - - this.dialogManager.open(title, message, sub_message, _("Clear"), _("Cancel"), () => { - this._clearHistory(); - } - ); - } - - _clearHistory () { - // Don't remove pinned items - this.historySection._getMenuItems().forEach(mItem => { - if (KEEP_SELECTED_ON_CLEAR === false || !mItem.currentlySelected) { - this._removeEntry(mItem, 'delete'); - } - }); - this._showNotification(_("Clipboard history cleared")); - } - - _removeAll () { - if (PRIVATEMODE) return; - var that = this; - - if (CONFIRM_ON_CLEAR) { - that._confirmRemoveAll(); - } else { - that._clearHistory(); - } - } - - _removeEntry (menuItem, event) { - let itemIdx = this.clipItemsRadioGroup.indexOf(menuItem); - - if(event === 'delete' && menuItem.currentlySelected) { - this.#clearClipboard(); - } - - menuItem.destroy(); - this.clipItemsRadioGroup.splice(itemIdx,1); - - if (menuItem.entry.isImage()) { - this.registry.deleteEntryFile(menuItem.entry); - } - - this._updateCache(); - this.#showElements(); - } - - _removeOldestEntries () { - let that = this; - - let clipItemsRadioGroupNoFavorite = that.clipItemsRadioGroup.filter( - item => item.entry.isFavorite() === false); - - const origSize = clipItemsRadioGroupNoFavorite.length; - - while (clipItemsRadioGroupNoFavorite.length > MAX_REGISTRY_LENGTH) { - let oldestNoFavorite = clipItemsRadioGroupNoFavorite.shift(); - that._removeEntry(oldestNoFavorite); - - clipItemsRadioGroupNoFavorite = that.clipItemsRadioGroup.filter( - item => item.entry.isFavorite() === false); - } - - if (clipItemsRadioGroupNoFavorite.length < origSize) { - that._updateCache(); - } - } - - _onMenuItemSelected (menuItem, autoSet) { - for (let otherMenuItem of menuItem.radioGroup) { - let clipContents = menuItem.clipContents; - - if (otherMenuItem === menuItem && clipContents) { - menuItem.setOrnament(PopupMenu.Ornament.DOT); - menuItem.currentlySelected = true; - if (autoSet !== false) - this.#updateClipboard(menuItem.entry); - } - else { - otherMenuItem.setOrnament(PopupMenu.Ornament.NONE); - otherMenuItem.currentlySelected = false; - } - } - } - - _selectMenuItem (menuItem, autoSet) { - this._onMenuItemSelected(menuItem, autoSet); - this.#updateIndicatorContent(menuItem.entry); - } - - _onMenuItemSelectedAndMenuClose (menuItem, autoSet) { - for (let otherMenuItem of menuItem.radioGroup) { - let clipContents = menuItem.clipContents; - - if (menuItem === otherMenuItem && clipContents) { - menuItem.setOrnament(PopupMenu.Ornament.DOT); - menuItem.currentlySelected = true; - if (autoSet !== false) - this.#updateClipboard(menuItem.entry); - } - else { - otherMenuItem.setOrnament(PopupMenu.Ornament.NONE); - otherMenuItem.currentlySelected = false; - } - } - - menuItem.menu.close(); - } - - _getCache () { - return this.registry.read(); - } - - #addToCache (entry) { - const entries = this.clipItemsRadioGroup - .map(menuItem => menuItem.entry) - .filter(entry => CACHE_ONLY_FAVORITE == false || entry.isFavorite()) - .concat([entry]); - this.registry.write(entries); - } - - _updateCache () { - const entries = this.clipItemsRadioGroup - .map(menuItem => menuItem.entry) - .filter(entry => CACHE_ONLY_FAVORITE == false || entry.isFavorite()); - - this.registry.write(entries); - } - - async _onSelectionChange (selection, selectionType, selectionSource) { - if (selectionType === Meta.SelectionType.SELECTION_CLIPBOARD) { - this._refreshIndicator(); - } - } - - async _refreshIndicator () { - if (PRIVATEMODE) return; // Private mode, do not. - - const focussedWindow = Shell.Global.get().display.focusWindow; - const wmClass = focussedWindow?.get_wm_class(); - - if (wmClass && EXCLUDED_APPS.includes(wmClass)) return; // Excluded app, do not. - - if (this.#refreshInProgress) return; - this.#refreshInProgress = true; - - try { - const result = await this.#getClipboardContent(); - - if (result) { - for (let menuItem of this.clipItemsRadioGroup) { - if (menuItem.entry.equals(result)) { - this._selectMenuItem(menuItem, false); - - if (!menuItem.entry.isFavorite() && MOVE_ITEM_FIRST) { - this._moveItemFirst(menuItem); - } - - return; - } - } - - this.#addToCache(result); - this._addEntry(result, true, false); - this._removeOldestEntries(); - if (NOTIFY_ON_COPY) { - this._showNotification(_("Copied to clipboard"), notif => { - notif.addAction(_('Cancel'), this._cancelNotification); - }); - } - } - } - catch (e) { - console.error('Clipboard Indicator: Failed to refresh indicator'); - console.error(e); - } - finally { - this.#refreshInProgress = false; - } - } - - _moveItemFirst (item) { - this._removeEntry(item); - this._addEntry(item.entry, item.currentlySelected, false); - this._updateCache(); - } - - _findItem (text) { - return this.clipItemsRadioGroup.filter( - item => item.clipContents === text)[0]; - } - - _getCurrentlySelectedItem () { - return this.clipItemsRadioGroup.find(item => item.currentlySelected); - } - - _getAllIMenuItems () { - return this.historySection._getMenuItems().concat(this.favoritesSection._getMenuItems()); - } - - _setupListener () { - const metaDisplay = Shell.Global.get().get_display(); - const selection = metaDisplay.get_selection(); - this._setupSelectionTracking(selection); - } - - _setupSelectionTracking (selection) { - this.selection = selection; - this._selectionOwnerChangedId = selection.connect('owner-changed', (selection, selectionType, selectionSource) => { - this._onSelectionChange(selection, selectionType, selectionSource); - }); - } - - _openSettings () { - this.extension.openSettings(); - } - - _initNotifSource () { - if (!this._notifSource) { - this._notifSource = new MessageTray.Source({ - title: 'Clipboard Indicator', - 'icon-name': INDICATOR_ICON - }); - - this._notifSource.connect('destroy', () => { - this._notifSource = null; - }); - - Main.messageTray.add(this._notifSource); - } - } - - _cancelNotification () { - if (this.clipItemsRadioGroup.length >= 2) { - let clipSecond = this.clipItemsRadioGroup.length - 2; - let previousClip = this.clipItemsRadioGroup[clipSecond]; - this.#updateClipboard(previousClip.entry); - previousClip.setOrnament(PopupMenu.Ornament.DOT); - previousClip.icoBtn.visible = false; - previousClip.currentlySelected = true; - } else { - this.#clearClipboard(); - } - let clipFirst = this.clipItemsRadioGroup.length - 1; - this._removeEntry(this.clipItemsRadioGroup[clipFirst]); - } - - _showNotification (message, transformFn) { - const dndOn = () => - !Main.panel.statusArea.dateMenu._indicator._settings.get_boolean( - 'show-banners', - ); - if (PRIVATEMODE || dndOn()) { - return; - } - - let notification = null; - - this._initNotifSource(); - - if (this._notifSource.count === 0) { - notification = new MessageTray.Notification({ - source: this._notifSource, - body: message, - 'is-transient': true - }); - } - else { - notification = this._notifSource.notifications[0]; - notification.body = message; - notification.clearActions(); - } - - if (typeof transformFn === 'function') { - transformFn(notification); - } - - this._notifSource.addNotification(notification); - } - - _createHistoryLabel () { - this._historyLabel = new St.Label({ - style_class: 'ci-notification-label', - text: '' - }); - - global.stage.add_child(this._historyLabel); - - this._historyLabel.hide(); - } - - togglePrivateMode () { - this.privateModeMenuItem.toggle(); - } - - _onPrivateModeSwitch () { - let that = this; - PRIVATEMODE = this.privateModeMenuItem.state; - // We hide the history in private ModeTypee because it will be out of sync (selected item will not reflect clipboard) - this.scrollViewMenuSection.actor.visible = !PRIVATEMODE; - this.scrollViewFavoritesMenuSection.actor.visible = !PRIVATEMODE; - // If we get out of private mode then we restore the clipboard to old state - if (!PRIVATEMODE) { - let selectList = this.clipItemsRadioGroup.filter((item) => !!item.currentlySelected); - - if (selectList.length) { - this._selectMenuItem(selectList[0]); - } else { - // Nothing to return to, let's empty it instead - this.#clearClipboard(); - } - - this.#getClipboardContent().then(entry => { - if (!entry) return; - this.#updateIndicatorContent(entry); - }).catch(e => console.error(e)); - - this.hbox.remove_style_class_name('private-mode'); - this.#showElements(); - } else { - this.hbox.add_style_class_name('private-mode'); - this.#updateIndicatorContent(null); - this.#hideElements(); - } - } - - _loadSettings () { - this._settingsChangedId = this.extension.settings.connect('changed', - this._onSettingsChange.bind(this)); - - this._fetchSettings(); - - if (ENABLE_KEYBINDING) - this._bindShortcuts(); - } - - _fetchSettings () { - const { settings } = this.extension; - MAX_REGISTRY_LENGTH = settings.get_int(PrefsFields.HISTORY_SIZE); - MAX_ENTRY_LENGTH = settings.get_int(PrefsFields.PREVIEW_SIZE); - CACHE_ONLY_FAVORITE = settings.get_boolean(PrefsFields.CACHE_ONLY_FAVORITE); - DELETE_ENABLED = settings.get_boolean(PrefsFields.DELETE); - MOVE_ITEM_FIRST = settings.get_boolean(PrefsFields.MOVE_ITEM_FIRST); - NOTIFY_ON_COPY = settings.get_boolean(PrefsFields.NOTIFY_ON_COPY); - NOTIFY_ON_CYCLE = settings.get_boolean(PrefsFields.NOTIFY_ON_CYCLE); - CONFIRM_ON_CLEAR = settings.get_boolean(PrefsFields.CONFIRM_ON_CLEAR); - ENABLE_KEYBINDING = settings.get_boolean(PrefsFields.ENABLE_KEYBINDING); - MAX_TOPBAR_LENGTH = settings.get_int(PrefsFields.TOPBAR_PREVIEW_SIZE); - TOPBAR_DISPLAY_MODE = settings.get_int(PrefsFields.TOPBAR_DISPLAY_MODE_ID); - CLEAR_ON_BOOT = settings.get_boolean(PrefsFields.CLEAR_ON_BOOT); - PASTE_ON_SELECT = settings.get_boolean(PrefsFields.PASTE_ON_SELECT); - DISABLE_DOWN_ARROW = settings.get_boolean(PrefsFields.DISABLE_DOWN_ARROW); - STRIP_TEXT = settings.get_boolean(PrefsFields.STRIP_TEXT); - KEEP_SELECTED_ON_CLEAR = settings.get_boolean(PrefsFields.KEEP_SELECTED_ON_CLEAR); - PASTE_BUTTON = settings.get_boolean(PrefsFields.PASTE_BUTTON); - PINNED_ON_BOTTOM = settings.get_boolean(PrefsFields.PINNED_ON_BOTTOM); - CACHE_IMAGES = settings.get_boolean(PrefsFields.CACHE_IMAGES); - EXCLUDED_APPS = settings.get_strv(PrefsFields.EXCLUDED_APPS); - } - - async _onSettingsChange () { - try { - var that = this; - - // Load the settings into variables - that._fetchSettings(); - - // Remove old entries in case the registry size changed - that._removeOldestEntries(); - - // Re-set menu-items lables in case preview size changed - this._getAllIMenuItems().forEach(function (mItem) { - that._setEntryLabel(mItem); - mItem.pasteBtn.visible = PASTE_BUTTON; - }); - - //update topbar - this._updateTopbarLayout(); - that.#updateIndicatorContent(await this.#getClipboardContent()); - - // Bind or unbind shortcuts - if (ENABLE_KEYBINDING) - that._bindShortcuts(); - else - that._unbindShortcuts(); - } catch (e) { - console.error('Clipboard Indicator: Failed to update registry'); - console.error(e); - } - } - - _bindShortcuts () { - this._unbindShortcuts(); - this._bindShortcut(PrefsFields.BINDING_CLEAR_HISTORY, this._removeAll); - this._bindShortcut(PrefsFields.BINDING_PREV_ENTRY, this._previousEntry); - this._bindShortcut(PrefsFields.BINDING_NEXT_ENTRY, this._nextEntry); - this._bindShortcut(PrefsFields.BINDING_TOGGLE_MENU, this._toggleMenu); - this._bindShortcut(PrefsFields.BINDING_PRIVATE_MODE, this.togglePrivateMode); - } - - _unbindShortcuts () { - this._shortcutsBindingIds.forEach( - (id) => Main.wm.removeKeybinding(id) - ); - - this._shortcutsBindingIds = []; - } - - _bindShortcut (name, cb) { - var ModeType = Shell.hasOwnProperty('ActionMode') ? - Shell.ActionMode : Shell.KeyBindingMode; - - Main.wm.addKeybinding( - name, - this.extension.settings, - Meta.KeyBindingFlags.NONE, - ModeType.ALL, - cb.bind(this) - ); - - this._shortcutsBindingIds.push(name); - } - - _updateTopbarLayout () { - if(TOPBAR_DISPLAY_MODE === 0){ - this.icon.visible = true; - this._buttonText.visible = false; - this._buttonImgPreview.visible = false; - this.show(); - } - if(TOPBAR_DISPLAY_MODE === 1){ - this.icon.visible = false; - this._buttonText.visible = true; - this._buttonImgPreview.visible = true; - this.show(); - } - if(TOPBAR_DISPLAY_MODE === 2){ - this.icon.visible = true; - this._buttonText.visible = true; - this._buttonImgPreview.visible = true; - this.show(); - } - if (TOPBAR_DISPLAY_MODE === 3) { - this.hide(); - } - if(!DISABLE_DOWN_ARROW) { - this._downArrow.visible = true; - } else { - this._downArrow.visible = false; - } - } - - _disconnectSettings () { - if (!this._settingsChangedId) - return; - - this.extension.settings.disconnect(this._settingsChangedId); - this._settingsChangedId = null; - } - - _disconnectSelectionListener () { - if (!this._selectionOwnerChangedId) - return; - - this.selection.disconnect(this._selectionOwnerChangedId); - } - - _clearDelayedSelectionTimeout () { - if (this._delayedSelectionTimeoutId) { - clearInterval(this._delayedSelectionTimeoutId); - } - } - - _selectEntryWithDelay (entry) { - let that = this; - that._selectMenuItem(entry, false); - - that._delayedSelectionTimeoutId = setTimeout(function () { - that._selectMenuItem(entry); //select the item - that._delayedSelectionTimeoutId = null; - }, DELAYED_SELECTION_TIMEOUT); - } - - _previousEntry () { - if (PRIVATEMODE) return; - let that = this; - - that._clearDelayedSelectionTimeout(); - - this._getAllIMenuItems().some(function (mItem, i, menuItems){ - if (mItem.currentlySelected) { - i--; //get the previous index - if (i < 0) i = menuItems.length - 1; //cycle if out of bound - let index = i + 1; //index to be displayed - - if(NOTIFY_ON_CYCLE) { - that._showNotification(index + ' / ' + menuItems.length + ': ' + menuItems[i].entry.getStringValue()); - } - if (MOVE_ITEM_FIRST) { - that._selectEntryWithDelay(menuItems[i]); - } - else { - that._selectMenuItem(menuItems[i]); - } - return true; - } - return false; - }); - } - - _nextEntry () { - if (PRIVATEMODE) return; - let that = this; - - that._clearDelayedSelectionTimeout(); - - this._getAllIMenuItems().some(function (mItem, i, menuItems){ - if (mItem.currentlySelected) { - i++; //get the next index - if (i === menuItems.length) i = 0; //cycle if out of bound - let index = i + 1; //index to be displayed - - if(NOTIFY_ON_CYCLE) { - that._showNotification(index + ' / ' + menuItems.length + ': ' + menuItems[i].entry.getStringValue()); - } - if (MOVE_ITEM_FIRST) { - that._selectEntryWithDelay(menuItems[i]); - } - else { - that._selectMenuItem(menuItems[i]); - } - return true; - } - return false; - }); - } - - _toggleMenu () { - this.menu.toggle(); - } - - #pasteItem (menuItem) { - this.menu.close(); - const currentlySelected = this._getCurrentlySelectedItem(); - this.preventIndicatorUpdate = true; - this.#updateClipboard(menuItem.entry); - this._pastingKeypressTimeout = setTimeout(() => { - if (this.keyboard.purpose === Clutter.InputContentPurpose.TERMINAL) { - this.keyboard.press(Clutter.KEY_Control_L); - this.keyboard.press(Clutter.KEY_Shift_L); - this.keyboard.press(Clutter.KEY_Insert); - this.keyboard.release(Clutter.KEY_Insert); - this.keyboard.release(Clutter.KEY_Shift_L); - this.keyboard.release(Clutter.KEY_Control_L); - } - else { - this.keyboard.press(Clutter.KEY_Shift_L); - this.keyboard.press(Clutter.KEY_Insert); - this.keyboard.release(Clutter.KEY_Insert); - this.keyboard.release(Clutter.KEY_Shift_L); - } - - this._pastingResetTimeout = setTimeout(() => { - this.preventIndicatorUpdate = false; - this.#updateClipboard(currentlySelected.entry); - }, 50); - }, 50); - } - - #clearTimeouts () { - if (this._imagePreviewTimeout) clearTimeout(this._imagePreviewTimeout); - if (this._setFocusOnOpenTimeout) clearTimeout(this._setFocusOnOpenTimeout); - if (this._pastingKeypressTimeout) clearTimeout(this._pastingKeypressTimeout); - if (this._pastingResetTimeout) clearTimeout(this._pastingResetTimeout); - } - - #clearClipboard () { - this.extension.clipboard.set_text(CLIPBOARD_TYPE, ""); - this.icon.icon_name = INDICATOR_ICON; - this.#updateIndicatorContent(null); - } - - #updateClipboard (entry) { - this.extension.clipboard.set_content(CLIPBOARD_TYPE, entry.mimetype(), entry.asBytes()); - this.#updateIndicatorContent(entry); - } - - async #getClipboardContent () { - const mimetypes = [ - "text/plain;charset=utf-8", - "UTF8_STRING", - "text/plain", - "STRING", - 'image/gif', - 'image/png', - 'image/jpg', - 'image/jpeg', - 'image/webp', - 'image/svg+xml', - 'text/html', - ]; - - for (let type of mimetypes) { - let result = await new Promise(resolve => this.extension.clipboard.get_content(CLIPBOARD_TYPE, type, (clipBoard, bytes) => { - if (bytes === null || bytes.get_size() === 0) { - resolve(null); - return; - } - - // HACK: workaround for GNOME 2nd+ copy mangling mimetypes https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/8233 - // In theory GNOME or XWayland should auto-convert this back to UTF8_STRING for legacy apps when it's needed https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/5300 - if (type === "UTF8_STRING") { - type = "text/plain;charset=utf-8"; - } - - const entry = new ClipboardEntry(type, bytes.get_data(), false); - if (CACHE_IMAGES && entry.isImage()) { - this.registry.writeEntryFile(entry); - } - resolve(entry); - })); - - if (result) { - if (!CACHE_IMAGES && result.isImage()) { - return null; - } - else { - return result; - } - } - } - - return null; - } -}); +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; +import Gio from 'gi://Gio'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; + +import * as AnimationUtils from 'resource:///org/gnome/shell/misc/animationUtils.js'; +import * as MessageTray from 'resource:///org/gnome/shell/ui/messageTray.js'; +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; +import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js'; +import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; +import { Extension, gettext as _ } from 'resource:///org/gnome/shell/extensions/extension.js'; + +import { Registry, ClipboardEntry } from './registry.js'; +import { DialogManager } from './confirmDialog.js'; +import { PrefsFields } from './constants.js'; +import { Keyboard } from './keyboard.js'; + +const CLIPBOARD_TYPE = St.ClipboardType.CLIPBOARD; + +const INDICATOR_ICON = 'edit-paste-symbolic'; +const INDICATOR_ICON_FULL = 'edit-pasted-symbolic'; + +const ext = Extension.lookupByURL(import.meta.url); + +let DELAYED_SELECTION_TIMEOUT = 750; +let MAX_REGISTRY_LENGTH = 15; +let MAX_ENTRY_LENGTH = 50; +let CACHE_ONLY_FAVORITE = false; +let DELETE_ENABLED = true; +let MOVE_ITEM_FIRST = false; +let ENABLE_KEYBINDING = true; +let PRIVATEMODE = false; +let NOTIFY_ON_COPY = true; +let NOTIFY_ON_CYCLE = true; +let CONFIRM_ON_CLEAR = true; +let MAX_TOPBAR_LENGTH = 15; +let TOPBAR_DISPLAY_MODE = 1; //0 - only icon, 1 - only clipboard content, 2 - both, 3 - neither +let CLEAR_ON_BOOT = false; +let PASTE_ON_SELECT = false; +let DISABLE_DOWN_ARROW = false; +let STRIP_TEXT = false; +let KEEP_SELECTED_ON_CLEAR = false; +let PASTE_BUTTON = true; +let PINNED_ON_BOTTOM = false; +let CACHE_IMAGES = true; +let EXCLUDED_APPS = []; +let CLEAR_HISTORY_ON_INTERVAL = false; +let CLEAR_HISTORY_INTERVAL = 60; +let NEXT_HISTORY_CLEAR = -1; +let CASE_SENSITIVE_SEARCH = false; +let REGEX_SEARCH = false; + +export default class ClipboardIndicatorExtension extends Extension { + enable () { + this.clipboardIndicator = new ClipboardIndicator({ + clipboard: St.Clipboard.get_default(), + settings: this.getSettings(), + openSettings: this.openPreferences, + uuid: this.uuid + }); + + Main.panel.addToStatusArea('clipboardIndicator', this.clipboardIndicator, 1); + } + + disable () { + this.clipboardIndicator.destroy(); + this.clipboardIndicator = null; + EXCLUDED_APPS = []; + } +} + +const ClipboardIndicator = GObject.registerClass({ + GTypeName: 'ClipboardIndicator' +}, class ClipboardIndicator extends PanelMenu.Button { + #refreshInProgress = false; + + destroy () { + this._disconnectSettings(); + this._unbindShortcuts(); + this._disconnectSelectionListener(); + this._clearDelayedSelectionTimeout(); + this.#clearTimeouts(); + this.dialogManager.destroy(); + this.keyboard.destroy(); + + super.destroy(); + } + + _init (extension) { + super._init(0.0, "ClipboardIndicator"); + this.extension = extension; + this.registry = new Registry(extension); + this.keyboard = new Keyboard(); + this._settingsChangedId = null; + this._selectionOwnerChangedId = null; + this._historyLabel = null; + this._buttonText = null; + this._disableDownArrow = null; + + this._shortcutsBindingIds = []; + this.clipItemsRadioGroup = []; + + let hbox = new St.BoxLayout({ + style_class: 'panel-status-menu-box clipboard-indicator-hbox' + }); + + this.hbox = hbox; + + this.icon = new St.Icon({ + icon_name: INDICATOR_ICON, + style_class: 'system-status-icon clipboard-indicator-icon' + }); + + this._buttonText = new St.Label({ + text: _('Text will be here'), + y_align: Clutter.ActorAlign.CENTER + }); + + this._buttonImgPreview = new St.Bin({ + style_class: 'clipboard-indicator-topbar-preview' + }); + + hbox.add_child(this.icon); + hbox.add_child(this._buttonText); + hbox.add_child(this._buttonImgPreview); + this._downArrow = PopupMenu.arrowIcon(St.Side.BOTTOM); + hbox.add_child(this._downArrow); + this.add_child(hbox); + this._createHistoryLabel(); + this._loadSettings(); + + if (CLEAR_ON_BOOT) this.registry.clearCacheFolder(); + + this.dialogManager = new DialogManager(); + this._buildMenu().then(() => { + this._updateTopbarLayout(); + this._setupListener(); + this._setupHistoryIntervalClearing(); + }); + } + + _getExtIcon(iconName) { + const ext = Extension.lookupByURL(import.meta.url); + const file = ext.dir.resolve_relative_path(`icons/${iconName}.svg`); + return new Gio.FileIcon({ file }); +} + + #updateIndicatorContent(entry) { + if (this.preventIndicatorUpdate || !this.icon) { + return; + } + const showTopbarContent = (TOPBAR_DISPLAY_MODE === 1 || TOPBAR_DISPLAY_MODE === 2); + if (!entry || PRIVATEMODE) { + if (showTopbarContent) { + this._buttonImgPreview.destroy_all_children(); + this._buttonText.set_text("..."); + } + this.icon.set_gicon(null); + this.icon.set_icon_name(INDICATOR_ICON); + } else { + this.icon.set_icon_name(null); + this.icon.set_gicon(this._getExtIcon(INDICATOR_ICON_FULL)); + if (entry.isText()) { + this._buttonText.set_text(this._truncate(entry.getStringValue(), MAX_TOPBAR_LENGTH)); + this._buttonImgPreview.destroy_all_children(); + } + else if (entry.isImage()) { + this._buttonText.set_text(''); + this._buttonImgPreview.destroy_all_children(); + this.registry.getEntryAsImage(entry).then(img => { + img.add_style_class_name('clipboard-indicator-img-preview'); + img.y_align = Clutter.ActorAlign.CENTER; + + // icon only renders properly in setTimeout for some arcane reason + this._imagePreviewTimeout = setTimeout(() => { + this._buttonImgPreview.set_child(img); + }, 0); + }); + } + } + } + + async _buildMenu () { + let that = this; + const clipHistory = await this._getCache(); + let lastIdx = clipHistory.length - 1; + let clipItemsArr = that.clipItemsRadioGroup; + + /* This create the search entry, which is add to a menuItem. + The searchEntry is connected to the function for research. + The menu itself is connected to some shitty hack in order to + grab the focus of the keyboard. */ + that._entryItem = new PopupMenu.PopupBaseMenuItem({ + reactive: false, + can_focus: false + }); + that.searchEntry = new St.Entry({ + name: 'searchEntry', + style_class: 'popup-menu-search-entry', + can_focus: true, + hint_text: _('Type here to search...'), + track_hover: true, + x_expand: true, + y_expand: true, + primary_icon: new St.Icon({ icon_name: 'edit-find-symbolic' }) + }); + + that.searchEntry.get_clutter_text().connect( + 'text-changed', + that._onSearchTextChanged.bind(that) + ); + + that._entryItem.add_child(that.searchEntry); + + that.menu.connect('open-state-changed', (self, open) => { + this._setFocusOnOpenTimeout = setTimeout(() => { + if (open) { + if (this.clipItemsRadioGroup.length > 0) { + that.searchEntry.set_text(''); + global.stage.set_key_focus(that.searchEntry); + } + else { + global.stage.set_key_focus(that.privateModeMenuItem); + } + } + }, 50); + }); + + // Create menu sections for items + // Favorites + that.favoritesSection = new PopupMenu.PopupMenuSection(); + + that.scrollViewFavoritesMenuSection = new PopupMenu.PopupMenuSection(); + this.favoritesScrollView = new St.ScrollView({ + style_class: 'ci-history-menu-section', + overlay_scrollbars: true + }); + this.favoritesScrollView.add_child(that.favoritesSection.actor); + + that.scrollViewFavoritesMenuSection.actor.add_child(this.favoritesScrollView); + this.favoritesSeparator = new PopupMenu.PopupSeparatorMenuItem(); + + // History + that.historySection = new PopupMenu.PopupMenuSection(); + + that.scrollViewMenuSection = new PopupMenu.PopupMenuSection(); + this.historyScrollView = new St.ScrollView({ + style_class: 'ci-main-menu-section ci-history-menu-section', + overlay_scrollbars: true + }); + this.historyScrollView.add_child(that.historySection.actor); + + that.scrollViewMenuSection.actor.add_child(this.historyScrollView); + + // Add separator + this.historySeparator = new PopupMenu.PopupSeparatorMenuItem(); + + // Add sections ordered according to settings + if (PINNED_ON_BOTTOM) { + that.menu.addMenuItem(that.scrollViewMenuSection); + that.menu.addMenuItem(that.scrollViewFavoritesMenuSection); + } + else { + that.menu.addMenuItem(that.scrollViewFavoritesMenuSection); + that.menu.addMenuItem(that.scrollViewMenuSection); + } + + // Private mode switch + that.privateModeMenuItem = new PopupMenu.PopupSwitchMenuItem( + _("Private mode"), PRIVATEMODE, { reactive: true }); + that.privateModeMenuItem.connect('toggled', + that._onPrivateModeSwitch.bind(that)); + that.privateModeMenuItem.insert_child_at_index( + new St.Icon({ + icon_name: 'security-medium-symbolic', + style_class: 'clipboard-menu-icon', + y_align: Clutter.ActorAlign.CENTER + }), + 0 + ); + that.menu.addMenuItem(that.privateModeMenuItem); + + // Add 'Clear' button which removes all items from cache + this.clearMenuItem = new PopupMenu.PopupMenuItem(_('Clear history')); + this.clearMenuItem.insert_child_at_index( + new St.Icon({ + icon_name: 'user-trash-symbolic', + style_class: 'clipboard-menu-icon', + y_align: Clutter.ActorAlign.CENTER + }), + 0 + ); + + let timerBox = new St.BoxLayout({ + x_align: Clutter.ActorAlign.END, + x_expand: true + }); + + this.timerLabel = new St.Label({ + text: '', + style: 'font-family: monospace;', + x_align: Clutter.ActorAlign.END, + x_expand: true + }); + + this.resetTimerButton = new St.Button({ + style_class: 'ci-action-btn', + can_focus: true, + child: new St.Icon({ + icon_name: 'view-refresh-symbolic', + style_class: 'system-status-icon', + icon_size: 14 + }), + x_align: Clutter.ActorAlign.END, + y_align: Clutter.ActorAlign.CENTER, + }); + + this.resetTimerButton.connect('clicked', () => { + this._scheduleNextHistoryClear(); + }); + + timerBox.add_child(this.timerLabel); + timerBox.add_child(this.resetTimerButton); + this.clearMenuItem.add_child(timerBox); + + this.clearMenuItem.connect('activate', that._removeAll.bind(that)); + + // Add 'Settings' menu item to open settings + this.settingsMenuItem = new PopupMenu.PopupMenuItem(_('Settings')); + this.settingsMenuItem.insert_child_at_index( + new St.Icon({ + icon_name: 'preferences-system-symbolic', + style_class: 'clipboard-menu-icon', + y_align: Clutter.ActorAlign.CENTER + }), + 0 + ); + that.menu.addMenuItem(this.settingsMenuItem); + this.settingsMenuItem.connect('activate', that._openSettings.bind(that)); + + // Empty state section + this.emptyStateSection = new St.BoxLayout({ + style_class: 'clipboard-indicator-empty-state', + vertical: true + }); + this.emptyStateSection.add_child(new St.Icon({ + icon_name: INDICATOR_ICON, + style_class: 'system-status-icon clipboard-indicator-icon', + x_align: Clutter.ActorAlign.CENTER + })); + this.emptyStateSection.add_child(new St.Label({ + text: _('Clipboard is empty'), + x_align: Clutter.ActorAlign.CENTER + })); + + // Add cached items + clipHistory.forEach(entry => this._addEntry(entry)); + + if (lastIdx >= 0) { + that._selectMenuItem(clipItemsArr[lastIdx]); + } + + this.#showElements(); + } + + #hideElements() { + if (this.menu.box.contains(this._entryItem)) this.menu.box.remove_child(this._entryItem); + if (this.menu.box.contains(this.favoritesSeparator)) this.menu.box.remove_child(this.favoritesSeparator); + if (this.menu.box.contains(this.historySeparator)) this.menu.box.remove_child(this.historySeparator); + if (this.menu.box.contains(this.clearMenuItem)) this.menu.box.remove_child(this.clearMenuItem); + if (this.menu.box.contains(this.emptyStateSection)) this.menu.box.remove_child(this.emptyStateSection); + } + + #showElements() { + if (this.clipItemsRadioGroup.length > 0) { + if (this.menu.box.contains(this._entryItem) === false) { + this.menu.box.insert_child_at_index(this._entryItem, 0); + } + if (this.menu.box.contains(this.clearMenuItem) === false) { + this.menu.box.insert_child_below(this.clearMenuItem, this.settingsMenuItem); + } + if (this.menu.box.contains(this.emptyStateSection) === true) { + this.menu.box.remove_child(this.emptyStateSection); + } + + if (this.favoritesSection._getMenuItems().length > 0) { + if (this.menu.box.contains(this.favoritesSeparator) === false) { + this.menu.box.insert_child_above(this.favoritesSeparator, this.scrollViewFavoritesMenuSection.actor); + } + } + else if (this.menu.box.contains(this.favoritesSeparator) === true) { + this.menu.box.remove_child(this.favoritesSeparator); + } + + if (this.historySection._getMenuItems().length > 0) { + if (this.menu.box.contains(this.historySeparator) === false) { + this.menu.box.insert_child_above(this.historySeparator, this.scrollViewMenuSection.actor); + } + } + else if (this.menu.box.contains(this.historySeparator) === true) { + this.menu.box.remove_child(this.historySeparator); + } + } + else if (this.menu.box.contains(this.emptyStateSection) === false) { + this.#renderEmptyState(); + } + } + + #renderEmptyState () { + this.#hideElements(); + this.menu.box.insert_child_at_index(this.emptyStateSection, 0); + } + + /* When text change, this function will check, for each item of the + historySection and favoritesSestion, if it should be visible or not (based on words contained + in the clipContents attribute of the item). It doesn't destroy or create + items. It the entry is empty, the section is restored with all items + set as visible. */ + _onSearchTextChanged () { + + // Text to be searched converted to lowercase if search is case insensitive + let searchedText = this.searchEntry.get_text(); + if (!CASE_SENSITIVE_SEARCH) searchedText = searchedText.toLowerCase(); + + if(searchedText === '') { + this._getAllIMenuItems().forEach(function(mItem){ + mItem.actor.visible = true; + }); + } + else { + this._getAllIMenuItems().forEach(function(mItem){ + // Clip content converted to lowercase if search is case insensitive + let text = mItem.clipContents; + if (!CASE_SENSITIVE_SEARCH) text = text.toLowerCase(); + + let isMatching = false; + if (REGEX_SEARCH){ + /* Regex flags: + - 'm' for multiline matching (when multiline content is copied) + - 'i' for case insensitive matching when search is not set to case sensitive + */ + let text_regex = new RegExp(searchedText, 'm' + (CASE_SENSITIVE_SEARCH ? '' : 'i')); + isMatching = text_regex.test(text); + }else{ + isMatching = text.indexOf(searchedText) >= 0; + } + mItem.actor.visible = isMatching + }); + } + } + + _truncate (string, length) { + let shortened = string.replace(/\s+/g, ' '); + + let chars = [...shortened] + if (chars.length > length) + shortened = chars.slice(0, length - 1).join('') + '...'; + + return shortened; + } + + _setEntryLabel (menuItem) { + const { entry } = menuItem; + if (entry.isText()) { + menuItem.label.set_text(this._truncate(entry.getStringValue(), MAX_ENTRY_LENGTH)); + } + else if (entry.isImage()) { + this.registry.getEntryAsImage(entry).then(img => { + img.add_style_class_name('clipboard-menu-img-preview'); + if (menuItem.previewImage) { + menuItem.remove_child(menuItem.previewImage); + } + menuItem.previewImage = img; + menuItem.insert_child_below(img, menuItem.label); + }); + } + } + + _findNextMenuItem (currentMenutItem) { + let currentIndex = this.clipItemsRadioGroup.indexOf(currentMenutItem); + + // for only one item + if(this.clipItemsRadioGroup.length === 1) { + return null; + } + + // when focus is in middle of the displayed list + for (let i = currentIndex - 1; i >= 0; i--) { + let menuItem = this.clipItemsRadioGroup[i]; + if (menuItem.actor.visible) { + return menuItem; + } + } + + // when focus is at the last element of the displayed list + let beforeMenuItem = this.clipItemsRadioGroup[currentIndex + 1]; + if(beforeMenuItem.actor.visible){ + return beforeMenuItem; + } + + return null; + } + + #selectNextMenuItem (menuItem) { + let nextMenuItem = this._findNextMenuItem(menuItem); + + if (nextMenuItem) { + nextMenuItem.actor.grab_key_focus(); + } else { + this.privateModeMenuItem.actor.grab_key_focus(); + } + } + + _addEntry (entry, autoSelect, autoSetClip) { + let menuItem = new PopupMenu.PopupMenuItem(''); + + menuItem.menu = this.menu; + menuItem.entry = entry; + menuItem.clipContents = entry.getStringValue(); + menuItem.radioGroup = this.clipItemsRadioGroup; + menuItem.buttonPressId = menuItem.connect('activate', + autoSet => this._onMenuItemSelectedAndMenuClose(menuItem, autoSet)); + menuItem.connect('key-focus-in', () => { + const viewToScroll = menuItem.entry.isFavorite() ? + this.favoritesScrollView : this.historyScrollView; + AnimationUtils.ensureActorVisibleInScrollView(viewToScroll, menuItem); + }); + menuItem.actor.connect('key-press-event', (actor, event) => { + switch (event.get_key_symbol()) { + case Clutter.KEY_Delete: + this.#selectNextMenuItem(menuItem); + this._removeEntry(menuItem, 'delete'); + break; + case Clutter.KEY_p: + this.#selectNextMenuItem(menuItem); + this._favoriteToggle(menuItem); + break; + case Clutter.KEY_v: + this.#pasteItem(menuItem); + break; + case Clutter.KEY_KP_Enter: + case Clutter.KEY_Return: + if (PASTE_ON_SELECT) { + this.#pasteItem(menuItem); + } + this._onMenuItemSelectedAndMenuClose(menuItem, true); + break; + } + }) + + this._setEntryLabel(menuItem); + this.clipItemsRadioGroup.push(menuItem); + + // Favorite button + let iconfav = new St.Icon({ + icon_name: 'view-pin-symbolic', + style_class: 'system-status-icon' + }); + + let icofavBtn = new St.Button({ + style_class: 'ci-pin-btn ci-action-btn', + can_focus: true, + child: iconfav, + x_align: Clutter.ActorAlign.END, + x_expand: true, + y_expand: true + }); + + menuItem.actor.add_child(icofavBtn); + menuItem.icofavBtn = icofavBtn; + menuItem.favoritePressId = icofavBtn.connect('clicked', + () => this._favoriteToggle(menuItem) + ); + + // Paste button + menuItem.pasteBtn = new St.Button({ + style_class: 'ci-action-btn', + can_focus: true, + child: new St.Icon({ + icon_name: 'edit-paste-symbolic', + style_class: 'system-status-icon' + }), + x_align: Clutter.ActorAlign.END, + x_expand: false, + y_expand: true, + visible: PASTE_BUTTON + }); + + menuItem.pasteBtn.connect('clicked', + () => this.#pasteItem(menuItem) + ); + + menuItem.actor.add_child(menuItem.pasteBtn); + + // Delete button + let icon = new St.Icon({ + icon_name: 'edit-delete-symbolic', //'mail-attachment-symbolic', + style_class: 'system-status-icon' + }); + + let icoBtn = new St.Button({ + style_class: 'ci-action-btn', + can_focus: true, + child: icon, + x_align: Clutter.ActorAlign.END, + x_expand: false, + y_expand: true + }); + + menuItem.actor.add_child(icoBtn); + menuItem.icoBtn = icoBtn; + menuItem.deletePressId = icoBtn.connect('clicked', + () => this._removeEntry(menuItem, 'delete') + ); + + if (entry.isFavorite()) { + this.favoritesSection.addMenuItem(menuItem, 0); + } else { + this.historySection.addMenuItem(menuItem, 0); + } + + if (autoSelect === true) { + this._selectMenuItem(menuItem, autoSetClip); + } + else { + menuItem.setOrnament(PopupMenu.Ornament.NONE); + } + + this.#showElements(); + } + + _favoriteToggle (menuItem) { + menuItem.entry.favorite = menuItem.entry.isFavorite() ? false : true; + this._moveItemFirst(menuItem); + this._updateCache(); + this.#showElements(); + } + + _confirmRemoveAll () { + const title = _("Clear all?"); + const message = _("Are you sure you want to delete all clipboard items?"); + const sub_message = _("This operation cannot be undone."); + + this.dialogManager.open(title, message, sub_message, _("Clear"), _("Cancel"), () => { + this._clearHistory(); + } + ); + } + + _clearHistory (invokedAutomatically = false) { + // Don't remove pinned items + this.historySection._getMenuItems().forEach(mItem => { + if (KEEP_SELECTED_ON_CLEAR === false || !mItem.currentlySelected) { + this._removeEntry(mItem, 'delete'); + } + }); + + if (!invokedAutomatically) { + this._showNotification(_("Clipboard history cleared")); + } + else { + this._showNotification(_("Clipboard history cleared automatically")); + } + } + + _removeAll () { + if (PRIVATEMODE) return; + var that = this; + + if (CONFIRM_ON_CLEAR) { + that._confirmRemoveAll(); + } else { + that._clearHistory(); + } + } + + _removeEntry (menuItem, event) { + let itemIdx = this.clipItemsRadioGroup.indexOf(menuItem); + + if(event === 'delete' && menuItem.currentlySelected) { + this.#clearClipboard(); + } + + menuItem.destroy(); + this.clipItemsRadioGroup.splice(itemIdx,1); + + if (menuItem.entry.isImage()) { + this.registry.deleteEntryFile(menuItem.entry); + } + + this._updateCache(); + this.#showElements(); + } + + _removeOldestEntries () { + let that = this; + + let clipItemsRadioGroupNoFavorite = that.clipItemsRadioGroup.filter( + item => item.entry.isFavorite() === false); + + const origSize = clipItemsRadioGroupNoFavorite.length; + + while (clipItemsRadioGroupNoFavorite.length > MAX_REGISTRY_LENGTH) { + let oldestNoFavorite = clipItemsRadioGroupNoFavorite.shift(); + that._removeEntry(oldestNoFavorite); + + clipItemsRadioGroupNoFavorite = that.clipItemsRadioGroup.filter( + item => item.entry.isFavorite() === false); + } + + if (clipItemsRadioGroupNoFavorite.length < origSize) { + that._updateCache(); + } + } + + _onMenuItemSelected (menuItem, autoSet) { + for (let otherMenuItem of menuItem.radioGroup) { + let clipContents = menuItem.clipContents; + + if (otherMenuItem === menuItem && clipContents) { + menuItem.setOrnament(PopupMenu.Ornament.DOT); + menuItem.currentlySelected = true; + if (autoSet !== false) + this.#updateClipboard(menuItem.entry); + } + else { + otherMenuItem.setOrnament(PopupMenu.Ornament.NONE); + otherMenuItem.currentlySelected = false; + } + } + } + + _selectMenuItem (menuItem, autoSet) { + this._onMenuItemSelected(menuItem, autoSet); + this.#updateIndicatorContent(menuItem.entry); + } + + _onMenuItemSelectedAndMenuClose (menuItem, autoSet) { + for (let otherMenuItem of menuItem.radioGroup) { + let clipContents = menuItem.clipContents; + + if (menuItem === otherMenuItem && clipContents) { + menuItem.setOrnament(PopupMenu.Ornament.DOT); + menuItem.currentlySelected = true; + if (autoSet !== false) + this.#updateClipboard(menuItem.entry); + } + else { + otherMenuItem.setOrnament(PopupMenu.Ornament.NONE); + otherMenuItem.currentlySelected = false; + } + } + + menuItem.menu.close(); + } + + _getCache () { + return this.registry.read(); + } + + #addToCache (entry) { + const entries = this.clipItemsRadioGroup + .map(menuItem => menuItem.entry) + .filter(entry => CACHE_ONLY_FAVORITE == false || entry.isFavorite()) + .concat([entry]); + this.registry.write(entries); + } + + _updateCache () { + const entries = this.clipItemsRadioGroup + .map(menuItem => menuItem.entry) + .filter(entry => CACHE_ONLY_FAVORITE == false || entry.isFavorite()); + + this.registry.write(entries); + } + + async _onSelectionChange (selection, selectionType, selectionSource) { + if (selectionType === Meta.SelectionType.SELECTION_CLIPBOARD) { + this._refreshIndicator(); + } + } + + async _refreshIndicator () { + if (PRIVATEMODE) return; // Private mode, do not. + + const focussedWindow = Shell.Global.get().display.focusWindow; + const wmClass = focussedWindow?.get_wm_class(); + + if (wmClass && EXCLUDED_APPS.includes(wmClass)) return; // Excluded app, do not. + + if (this.#refreshInProgress) return; + this.#refreshInProgress = true; + + try { + const result = await this.#getClipboardContent(); + + if (result) { + for (let menuItem of this.clipItemsRadioGroup) { + if (menuItem.entry.equals(result)) { + this._selectMenuItem(menuItem, false); + + if (!menuItem.entry.isFavorite() && MOVE_ITEM_FIRST) { + this._moveItemFirst(menuItem); + } + + return; + } + } + + this.#addToCache(result); + this._addEntry(result, true, false); + this._removeOldestEntries(); + if (NOTIFY_ON_COPY) { + this._showNotification(_("Copied to clipboard"), notif => { + notif.addAction(_('Cancel'), this._cancelNotification); + }); + } + } + } + catch (e) { + console.error('Clipboard Indicator: Failed to refresh indicator'); + console.error(e); + } + finally { + this.#refreshInProgress = false; + } + } + + _moveItemFirst (item) { + this._removeEntry(item); + this._addEntry(item.entry, item.currentlySelected, false); + this._updateCache(); + } + + _findItem (text) { + return this.clipItemsRadioGroup.filter( + item => item.clipContents === text)[0]; + } + + _getCurrentlySelectedItem () { + return this.clipItemsRadioGroup.find(item => item.currentlySelected); + } + + _getAllIMenuItems () { + return this.historySection._getMenuItems().concat(this.favoritesSection._getMenuItems()); + } + + _setupListener () { + const metaDisplay = Shell.Global.get().get_display(); + const selection = metaDisplay.get_selection(); + this._setupSelectionTracking(selection); + } + + _setupSelectionTracking (selection) { + this.selection = selection; + this._selectionOwnerChangedId = selection.connect('owner-changed', (selection, selectionType, selectionSource) => { + this._onSelectionChange(selection, selectionType, selectionSource); + }); + } + + _setupHistoryIntervalClearing() { + this._fetchSettings(); + + if (this._intervalSettingChangedId) { + this.extension.settings.disconnect(this._intervalSettingChangedId); + this._intervalSettingChangedId = null; + } + if (this._intervalToggleChangedId) { + this.extension.settings.disconnect(this._intervalToggleChangedId); + this._intervalToggleChangedId = null; + } + if (this._historyClearTimeoutId) { + clearTimeout(this._historyClearTimeoutId); + this._historyClearTimeoutId = null; + } + + this._intervalSettingChangedId = this.extension.settings.connect( + `changed::${PrefsFields.CLEAR_HISTORY_INTERVAL}`, + this._onHistoryIntervalClearSettingsChanged.bind(this) + ); + this._intervalToggleChangedId = this.extension.settings.connect( + `changed::${PrefsFields.CLEAR_HISTORY_ON_INTERVAL}`, + this._onHistoryIntervalClearSettingsChanged.bind(this) + ); + + + + if (!CLEAR_HISTORY_ON_INTERVAL) { + this._updateIntervalTimer(); + return; + } + + const currentTime = Math.ceil(new Date().getTime() / 1000); + + if (NEXT_HISTORY_CLEAR === -1) { //new timer + this._scheduleNextHistoryClear(); + } + else if (NEXT_HISTORY_CLEAR < currentTime) { //timer expired + this._clearHistory(true); + this._scheduleNextHistoryClear(); + } + else { //timer already set, but not expired + const timeoutMs = (NEXT_HISTORY_CLEAR - currentTime) * 1000; + this._historyClearTimeoutId = setTimeout(() => { + this._clearHistory(true); + this._scheduleNextHistoryClear(); + }, timeoutMs); + this._timerIntervalId = setInterval(() => { + this._updateIntervalTimer(); + }, 1000); + } + } + + _onHistoryIntervalClearSettingsChanged(_settings, key) { + this._fetchSettings(); + if (key === PrefsFields.CLEAR_HISTORY_INTERVAL) { + this._scheduleNextHistoryClear(); + } + else if (key === PrefsFields.CLEAR_HISTORY_ON_INTERVAL) { + if (CLEAR_HISTORY_ON_INTERVAL) { + this._resetHistoryClearTimer(); + this._setupHistoryIntervalClearing(); + } else { + this._resetHistoryClearTimer(); + } + } + } + + _scheduleNextHistoryClear() { + this._fetchSettings(); + + clearInterval(this._timerIntervalId); + if (this._historyClearTimeoutId) { + clearTimeout(this._historyClearTimeoutId); + this._historyClearTimeoutId = null; + } + + if(!CLEAR_HISTORY_ON_INTERVAL) { + this._resetHistoryClearTimer(); + return; + } + + const currentTime = Math.ceil(new Date().getTime() / 1000); + NEXT_HISTORY_CLEAR = currentTime + CLEAR_HISTORY_INTERVAL * 60; + const timeoutMs = (NEXT_HISTORY_CLEAR - currentTime) * 1000; + + this.extension.settings.set_int(PrefsFields.NEXT_HISTORY_CLEAR, NEXT_HISTORY_CLEAR); + + this._updateIntervalTimer(); + this._timerIntervalId = setInterval(() => { + this._updateIntervalTimer(); + }, 1000); + + this._historyClearTimeoutId = setTimeout(() => { + this._clearHistory(true); + this._scheduleNextHistoryClear(); + }, timeoutMs); + } + + _resetHistoryClearTimer() { + //basically just reset and stop the timer + if (this._historyClearTimeoutId) { + clearTimeout(this._historyClearTimeoutId); + this._historyClearTimeoutId = null; + } + clearInterval(this._timerIntervalId); + this._timerIntervalId = null; + this._updateIntervalTimer(); + this.extension.settings.set_int(PrefsFields.NEXT_HISTORY_CLEAR, -1); + } + + _updateIntervalTimer() { + this._fetchSettings(); + this.resetTimerButton.visible = CLEAR_HISTORY_ON_INTERVAL; + this.timerLabel.visible = CLEAR_HISTORY_ON_INTERVAL; + if (!CLEAR_HISTORY_ON_INTERVAL) return; + + + let currentTime = Math.ceil(new Date().getTime() / 1000); + let timeLeft = NEXT_HISTORY_CLEAR - currentTime; + + if (timeLeft <= 0) { + this.timerLabel.set_text(''); + return; + } + + let hours = Math.floor(timeLeft / 3600); + let minutes = Math.floor((timeLeft % 3600) / 60); + let seconds = Math.floor(timeLeft % 60); + + let formattedTime = ''; + if (hours > 0) { + formattedTime += `${hours}h `; + } + if (minutes > 0) { + formattedTime += `${minutes}m `; + } + formattedTime += `${seconds}s`; + this.timerLabel.set_text(formattedTime); + } + + _openSettings () { + this.extension.openSettings(); + } + + _initNotifSource () { + if (!this._notifSource) { + this._notifSource = new MessageTray.Source({ + title: 'Clipboard Indicator', + 'icon-name': INDICATOR_ICON + }); + + this._notifSource.connect('destroy', () => { + this._notifSource = null; + }); + + Main.messageTray.add(this._notifSource); + } + } + + _cancelNotification () { + if (this.clipItemsRadioGroup.length >= 2) { + let clipSecond = this.clipItemsRadioGroup.length - 2; + let previousClip = this.clipItemsRadioGroup[clipSecond]; + this.#updateClipboard(previousClip.entry); + previousClip.setOrnament(PopupMenu.Ornament.DOT); + previousClip.icoBtn.visible = false; + previousClip.currentlySelected = true; + } else { + this.#clearClipboard(); + } + let clipFirst = this.clipItemsRadioGroup.length - 1; + this._removeEntry(this.clipItemsRadioGroup[clipFirst]); + } + + _showNotification (message, transformFn) { + const dndOn = () => + !Main.panel.statusArea.dateMenu._indicator._settings.get_boolean( + 'show-banners', + ); + if (PRIVATEMODE || dndOn()) { + return; + } + + let notification = null; + + this._initNotifSource(); + + if (this._notifSource.count === 0) { + notification = new MessageTray.Notification({ + source: this._notifSource, + body: message, + 'is-transient': true + }); + } + else { + notification = this._notifSource.notifications[0]; + notification.body = message; + notification.clearActions(); + } + + if (typeof transformFn === 'function') { + transformFn(notification); + } + + this._notifSource.addNotification(notification); + } + + _createHistoryLabel () { + this._historyLabel = new St.Label({ + style_class: 'ci-notification-label', + text: '' + }); + + global.stage.add_child(this._historyLabel); + + this._historyLabel.hide(); + } + + togglePrivateMode () { + this.privateModeMenuItem.toggle(); + } + + _onPrivateModeSwitch () { + let that = this; + PRIVATEMODE = this.privateModeMenuItem.state; + // We hide the history in private ModeTypee because it will be out of sync (selected item will not reflect clipboard) + this.scrollViewMenuSection.actor.visible = !PRIVATEMODE; + this.scrollViewFavoritesMenuSection.actor.visible = !PRIVATEMODE; + // If we get out of private mode then we restore the clipboard to old state + if (!PRIVATEMODE) { + let selectList = this.clipItemsRadioGroup.filter((item) => !!item.currentlySelected); + + if (selectList.length) { + this._selectMenuItem(selectList[0]); + } else { + // Nothing to return to, let's empty it instead + this.#clearClipboard(); + } + + this.#getClipboardContent().then(entry => { + if (!entry) return; + this.#updateIndicatorContent(entry); + }).catch(e => console.error(e)); + + this.hbox.remove_style_class_name('private-mode'); + this.#showElements(); + } else { + this.hbox.add_style_class_name('private-mode'); + this.#updateIndicatorContent(null); + this.#hideElements(); + } + } + + _loadSettings () { + this._settingsChangedId = this.extension.settings.connect('changed', + this._onSettingsChange.bind(this)); + + this._fetchSettings(); + + if (ENABLE_KEYBINDING) + this._bindShortcuts(); + } + + _fetchSettings () { + const { settings } = this.extension; + MAX_REGISTRY_LENGTH = settings.get_int(PrefsFields.HISTORY_SIZE); + MAX_ENTRY_LENGTH = settings.get_int(PrefsFields.PREVIEW_SIZE); + CACHE_ONLY_FAVORITE = settings.get_boolean(PrefsFields.CACHE_ONLY_FAVORITE); + DELETE_ENABLED = settings.get_boolean(PrefsFields.DELETE); + MOVE_ITEM_FIRST = settings.get_boolean(PrefsFields.MOVE_ITEM_FIRST); + NOTIFY_ON_COPY = settings.get_boolean(PrefsFields.NOTIFY_ON_COPY); + NOTIFY_ON_CYCLE = settings.get_boolean(PrefsFields.NOTIFY_ON_CYCLE); + CONFIRM_ON_CLEAR = settings.get_boolean(PrefsFields.CONFIRM_ON_CLEAR); + ENABLE_KEYBINDING = settings.get_boolean(PrefsFields.ENABLE_KEYBINDING); + MAX_TOPBAR_LENGTH = settings.get_int(PrefsFields.TOPBAR_PREVIEW_SIZE); + TOPBAR_DISPLAY_MODE = settings.get_int(PrefsFields.TOPBAR_DISPLAY_MODE_ID); + CLEAR_ON_BOOT = settings.get_boolean(PrefsFields.CLEAR_ON_BOOT); + PASTE_ON_SELECT = settings.get_boolean(PrefsFields.PASTE_ON_SELECT); + DISABLE_DOWN_ARROW = settings.get_boolean(PrefsFields.DISABLE_DOWN_ARROW); + STRIP_TEXT = settings.get_boolean(PrefsFields.STRIP_TEXT); + KEEP_SELECTED_ON_CLEAR = settings.get_boolean(PrefsFields.KEEP_SELECTED_ON_CLEAR); + PASTE_BUTTON = settings.get_boolean(PrefsFields.PASTE_BUTTON); + PINNED_ON_BOTTOM = settings.get_boolean(PrefsFields.PINNED_ON_BOTTOM); + CACHE_IMAGES = settings.get_boolean(PrefsFields.CACHE_IMAGES); + EXCLUDED_APPS = settings.get_strv(PrefsFields.EXCLUDED_APPS); + CLEAR_HISTORY_ON_INTERVAL = settings.get_boolean(PrefsFields.CLEAR_HISTORY_ON_INTERVAL); + CLEAR_HISTORY_INTERVAL = settings.get_int(PrefsFields.CLEAR_HISTORY_INTERVAL); + NEXT_HISTORY_CLEAR = settings.get_int(PrefsFields.NEXT_HISTORY_CLEAR); + CASE_SENSITIVE_SEARCH = settings.get_boolean(PrefsFields.CASE_SENSITIVE_SEARCH); + REGEX_SEARCH = settings.get_boolean(PrefsFields.REGEX_SEARCH); + } + + async _onSettingsChange () { + try { + var that = this; + + // Load the settings into variables + that._fetchSettings(); + + // Remove old entries in case the registry size changed + that._removeOldestEntries(); + + // Re-set menu-items lables in case preview size changed + this._getAllIMenuItems().forEach(function (mItem) { + that._setEntryLabel(mItem); + mItem.pasteBtn.visible = PASTE_BUTTON; + }); + + //update topbar + this._updateTopbarLayout(); + that.#updateIndicatorContent(await this.#getClipboardContent()); + + // Bind or unbind shortcuts + if (ENABLE_KEYBINDING) + that._bindShortcuts(); + else + that._unbindShortcuts(); + } catch (e) { + console.error('Clipboard Indicator: Failed to update registry'); + console.error(e); + } + } + + _bindShortcuts () { + this._unbindShortcuts(); + this._bindShortcut(PrefsFields.BINDING_CLEAR_HISTORY, this._removeAll); + this._bindShortcut(PrefsFields.BINDING_PREV_ENTRY, this._previousEntry); + this._bindShortcut(PrefsFields.BINDING_NEXT_ENTRY, this._nextEntry); + this._bindShortcut(PrefsFields.BINDING_TOGGLE_MENU, this._toggleMenu); + this._bindShortcut(PrefsFields.BINDING_PRIVATE_MODE, this.togglePrivateMode); + } + + _unbindShortcuts () { + this._shortcutsBindingIds.forEach( + (id) => Main.wm.removeKeybinding(id) + ); + + this._shortcutsBindingIds = []; + } + + _bindShortcut (name, cb) { + var ModeType = Shell.hasOwnProperty('ActionMode') ? + Shell.ActionMode : Shell.KeyBindingMode; + + Main.wm.addKeybinding( + name, + this.extension.settings, + Meta.KeyBindingFlags.NONE, + ModeType.ALL, + cb.bind(this) + ); + + this._shortcutsBindingIds.push(name); + } + + _updateTopbarLayout () { + if(TOPBAR_DISPLAY_MODE === 0){ + this.icon.visible = true; + this._buttonText.visible = false; + this._buttonImgPreview.visible = false; + this.show(); + } + if(TOPBAR_DISPLAY_MODE === 1){ + this.icon.visible = false; + this._buttonText.visible = true; + this._buttonImgPreview.visible = true; + this.show(); + } + if(TOPBAR_DISPLAY_MODE === 2){ + this.icon.visible = true; + this._buttonText.visible = true; + this._buttonImgPreview.visible = true; + this.show(); + } + if (TOPBAR_DISPLAY_MODE === 3) { + this.hide(); + } + if(!DISABLE_DOWN_ARROW) { + this._downArrow.visible = true; + } else { + this._downArrow.visible = false; + } + } + + _disconnectSettings () { + if (!this._settingsChangedId) + return; + + this.extension.settings.disconnect(this._settingsChangedId); + this._settingsChangedId = null; + + if (this._intervalSettingChangedId) { + this.extension.settings.disconnect(this._intervalSettingChangedId); + this._intervalSettingChangedId = null; + } + + if (this._intervalToggleChangedId) { + this.extension.settings.disconnect(this._intervalToggleChangedId); + this._intervalToggleChangedId = null; + } + + if (this._historyClearTimeoutId) { + clearTimeout(this._historyClearTimeoutId); + this._historyClearTimeoutId = null; + } + } + + _disconnectSelectionListener () { + if (!this._selectionOwnerChangedId) + return; + + this.selection.disconnect(this._selectionOwnerChangedId); + } + + _clearDelayedSelectionTimeout () { + if (this._delayedSelectionTimeoutId) { + clearInterval(this._delayedSelectionTimeoutId); + } + } + + _selectEntryWithDelay (entry) { + let that = this; + that._selectMenuItem(entry, false); + + that._delayedSelectionTimeoutId = setTimeout(function () { + that._selectMenuItem(entry); //select the item + that._delayedSelectionTimeoutId = null; + }, DELAYED_SELECTION_TIMEOUT); + } + + _previousEntry () { + if (PRIVATEMODE) return; + let that = this; + + that._clearDelayedSelectionTimeout(); + + this._getAllIMenuItems().some(function (mItem, i, menuItems){ + if (mItem.currentlySelected) { + i--; //get the previous index + if (i < 0) i = menuItems.length - 1; //cycle if out of bound + let index = i + 1; //index to be displayed + + if(NOTIFY_ON_CYCLE) { + that._showNotification(index + ' / ' + menuItems.length + ': ' + menuItems[i].entry.getStringValue()); + } + if (MOVE_ITEM_FIRST) { + that._selectEntryWithDelay(menuItems[i]); + } + else { + that._selectMenuItem(menuItems[i]); + } + return true; + } + return false; + }); + } + + _nextEntry () { + if (PRIVATEMODE) return; + let that = this; + + that._clearDelayedSelectionTimeout(); + + this._getAllIMenuItems().some(function (mItem, i, menuItems){ + if (mItem.currentlySelected) { + i++; //get the next index + if (i === menuItems.length) i = 0; //cycle if out of bound + let index = i + 1; //index to be displayed + + if(NOTIFY_ON_CYCLE) { + that._showNotification(index + ' / ' + menuItems.length + ': ' + menuItems[i].entry.getStringValue()); + } + if (MOVE_ITEM_FIRST) { + that._selectEntryWithDelay(menuItems[i]); + } + else { + that._selectMenuItem(menuItems[i]); + } + return true; + } + return false; + }); + } + + _toggleMenu () { + this.menu.toggle(); + } + + #pasteItem (menuItem) { + this.menu.close(); + const currentlySelected = this._getCurrentlySelectedItem(); + this.preventIndicatorUpdate = true; + this.#updateClipboard(menuItem.entry); + this._pastingKeypressTimeout = setTimeout(() => { + if (this.keyboard.purpose === Clutter.InputContentPurpose.TERMINAL) { + this.keyboard.press(Clutter.KEY_Control_L); + this.keyboard.press(Clutter.KEY_Shift_L); + this.keyboard.press(Clutter.KEY_Insert); + this.keyboard.release(Clutter.KEY_Insert); + this.keyboard.release(Clutter.KEY_Shift_L); + this.keyboard.release(Clutter.KEY_Control_L); + } + else { + this.keyboard.press(Clutter.KEY_Shift_L); + this.keyboard.press(Clutter.KEY_Insert); + this.keyboard.release(Clutter.KEY_Insert); + this.keyboard.release(Clutter.KEY_Shift_L); + } + + this._pastingResetTimeout = setTimeout(() => { + this.preventIndicatorUpdate = false; + this.#updateClipboard(currentlySelected.entry); + }, 50); + }, 50); + } + + #clearTimeouts () { + if (this._imagePreviewTimeout) clearTimeout(this._imagePreviewTimeout); + if (this._setFocusOnOpenTimeout) clearTimeout(this._setFocusOnOpenTimeout); + if (this._pastingKeypressTimeout) clearTimeout(this._pastingKeypressTimeout); + if (this._pastingResetTimeout) clearTimeout(this._pastingResetTimeout); + if (this._historyClearTimeoutId) clearTimeout(this._historyClearTimeoutId); + if (this._timerIntervalId) clearInterval(this._timerIntervalId); + } + + #clearClipboard () { + this.extension.clipboard.set_text(CLIPBOARD_TYPE, ""); + this.icon.icon_name = INDICATOR_ICON; + this.#updateIndicatorContent(null); + } + + #updateClipboard (entry) { + this.extension.clipboard.set_content(CLIPBOARD_TYPE, entry.mimetype(), entry.asBytes()); + this.#updateIndicatorContent(entry); + } + + async #getClipboardContent () { + const mimetypes = [ + "text/plain;charset=utf-8", + "UTF8_STRING", + "text/plain", + "STRING", + 'image/gif', + 'image/png', + 'image/jpg', + 'image/jpeg', + 'image/webp', + 'image/svg+xml', + 'text/html', + ]; + + for (let type of mimetypes) { + let result = await new Promise(resolve => this.extension.clipboard.get_content(CLIPBOARD_TYPE, type, (clipBoard, bytes) => { + if (bytes === null || bytes.get_size() === 0) { + resolve(null); + return; + } + + // HACK: workaround for GNOME 2nd+ copy mangling mimetypes https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/8233 + // In theory GNOME or XWayland should auto-convert this back to UTF8_STRING for legacy apps when it's needed https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/5300 + if (type === "UTF8_STRING") { + type = "text/plain;charset=utf-8"; + } + + const entry = new ClipboardEntry(type, bytes.get_data(), false); + if (CACHE_IMAGES && entry.isImage()) { + this.registry.writeEntryFile(entry); + } + resolve(entry); + })); + + if (result) { + if (!CACHE_IMAGES && result.isImage()) { + return null; + } + else { + return result; + } + } + } + + return null; + } +}); \ No newline at end of file From ef4df5e3ea5988fc446aa92bd54288e39fd778f6 Mon Sep 17 00:00:00 2001 From: IlCraccatore2011 Date: Wed, 28 Jan 2026 21:50:18 +0100 Subject: [PATCH 3/3] Updated languages, fixed search box theme bug, added new icon when there is clipboard in the system --- extension.js | 127 ++++++++++++++---- icons/{ => Adwaita}/edit-pasted-symbolic.svg | 0 .../edit-pasted-symbolic.svg.license | 0 icons/Flat/edit-pasted-symbolic.svg | 12 ++ icons/Flat/edit-pasted-symbolic.svg.license | 3 + icons/Yaru/edit-pasted-symbolic.svg | 3 + icons/Yaru/edit-pasted-symbolic.svg.license | 3 + 7 files changed, 123 insertions(+), 25 deletions(-) rename icons/{ => Adwaita}/edit-pasted-symbolic.svg (100%) rename icons/{ => Adwaita}/edit-pasted-symbolic.svg.license (100%) create mode 100644 icons/Flat/edit-pasted-symbolic.svg create mode 100644 icons/Flat/edit-pasted-symbolic.svg.license create mode 100644 icons/Yaru/edit-pasted-symbolic.svg create mode 100644 icons/Yaru/edit-pasted-symbolic.svg.license diff --git a/extension.js b/extension.js index ecfbacd8..f1963462 100644 --- a/extension.js +++ b/extension.js @@ -84,7 +84,11 @@ const ClipboardIndicator = GObject.registerClass({ this.#clearTimeouts(); this.dialogManager.destroy(); this.keyboard.destroy(); + if (this._iconThemeChangedId) { + this._interfaceSettings.disconnect(this._iconThemeChangedId); + this._iconThemeChangedId = 0; + } super.destroy(); } @@ -112,12 +116,23 @@ const ClipboardIndicator = GObject.registerClass({ icon_name: INDICATOR_ICON, style_class: 'system-status-icon clipboard-indicator-icon' }); + + this._interfaceSettings = new Gio.Settings({ + schema_id: 'org.gnome.desktop.interface' + }); + + this._iconThemeChangedId = this._interfaceSettings.connect('changed::icon-theme', () => { + this.#updateIndicatorContent( + this._getCurrentlySelectedItem()?.entry ?? null + ); + }); this._buttonText = new St.Label({ text: _('Text will be here'), y_align: Clutter.ActorAlign.CENTER }); + this._buttonImgPreview = new St.Bin({ style_class: 'clipboard-indicator-topbar-preview' }); @@ -134,34 +149,82 @@ const ClipboardIndicator = GObject.registerClass({ if (CLEAR_ON_BOOT) this.registry.clearCacheFolder(); this.dialogManager = new DialogManager(); - this._buildMenu().then(() => { + this._buildMenu().then(async() => { this._updateTopbarLayout(); this._setupListener(); this._setupHistoryIntervalClearing(); + const entry = await this.#getClipboardContent(); + this.#updateIndicatorContent(entry); }); } + + _getIconThemeName() { + const ext = Extension.lookupByURL(import.meta.url); + const settings = new Gio.Settings({ + schema_id: 'org.gnome.desktop.interface' + }); + return settings.get_string('icon-theme'); + } _getExtIcon(iconName) { - const ext = Extension.lookupByURL(import.meta.url); - const file = ext.dir.resolve_relative_path(`icons/${iconName}.svg`); - return new Gio.FileIcon({ file }); -} + const themeName = this._getIconThemeName(); + const ext = Extension.lookupByURL(import.meta.url); + let relativePath; + + if (themeName.startsWith('Adwaita')) { + relativePath = `icons/Adwaita/${iconName}.svg`; + } else if (themeName.startsWith('Flat')) { + relativePath = `icons/Flat/${iconName}.svg`; + } else if (themeName.startsWith('Yaru')) { + relativePath = `icons/Yaru/${iconName}.svg`; + } else { + return null; + } + const file = ext.dir.resolve_relative_path(relativePath); + if (!file.query_exists(null)) { + return null; + } + return new Gio.FileIcon({ file }); + } - #updateIndicatorContent(entry) { - if (this.preventIndicatorUpdate || !this.icon) { - return; + #updateIndicatorContent(entry) { + if (this.preventIndicatorUpdate || !this.icon) return; + const showTopbarContent = (TOPBAR_DISPLAY_MODE === 1 || TOPBAR_DISPLAY_MODE === 2); + if (showTopbarContent) { + this._buttonImgPreview.destroy_all_children(); + this._buttonText.set_text(entry ? this._truncate(entry.getStringValue(), MAX_TOPBAR_LENGTH) : "..."); } - const showTopbarContent = (TOPBAR_DISPLAY_MODE === 1 || TOPBAR_DISPLAY_MODE === 2); + + const isYaru = this._getIconThemeName().startsWith('Yaru'); + if (!entry || PRIVATEMODE) { - if (showTopbarContent) { - this._buttonImgPreview.destroy_all_children(); - this._buttonText.set_text("..."); - } - this.icon.set_gicon(null); - this.icon.set_icon_name(INDICATOR_ICON); + if (isYaru) { + const gicon = this._getExtIcon(INDICATOR_ICON_FULL); + if (gicon) { + this.icon.set_icon_name(null); + this.icon.set_gicon(gicon); + } else { + this.icon.set_gicon(null); + this.icon.set_icon_name(INDICATOR_ICON); + } + } else { + this.icon.set_gicon(null); + this.icon.set_icon_name(INDICATOR_ICON); + } } else { - this.icon.set_icon_name(null); - this.icon.set_gicon(this._getExtIcon(INDICATOR_ICON_FULL)); + if (isYaru) { + this.icon.set_gicon(null); + this.icon.set_icon_name(INDICATOR_ICON); + } else { + const gicon = this._getExtIcon(INDICATOR_ICON_FULL); + if (gicon) { + this.icon.set_icon_name(null); + this.icon.set_gicon(gicon); + } else { + this.icon.set_gicon(null); + this.icon.set_icon_name(INDICATOR_ICON); + } + } if (entry.isText()) { this._buttonText.set_text(this._truncate(entry.getStringValue(), MAX_TOPBAR_LENGTH)); this._buttonImgPreview.destroy_all_children(); @@ -299,6 +362,7 @@ const ClipboardIndicator = GObject.registerClass({ }); this.timerLabel = new St.Label({ + text: '', style: 'font-family: monospace;', x_align: Clutter.ActorAlign.END, @@ -345,11 +409,18 @@ const ClipboardIndicator = GObject.registerClass({ style_class: 'clipboard-indicator-empty-state', vertical: true }); - this.emptyStateSection.add_child(new St.Icon({ - icon_name: INDICATOR_ICON, - style_class: 'system-status-icon clipboard-indicator-icon', - x_align: Clutter.ActorAlign.CENTER - })); + let emptyIcon = null; + if (this._getIconThemeName().startsWith('Yaru')) { + const gicon = this._getExtIcon(INDICATOR_ICON_FULL); + if (gicon) { + emptyIcon = new St.Icon({ gicon, style_class: 'system-status-icon clipboard-indicator-icon', x_align: Clutter.ActorAlign.CENTER }); + } else { + emptyIcon = new St.Icon({ icon_name: INDICATOR_ICON, style_class: 'system-status-icon clipboard-indicator-icon', x_align: Clutter.ActorAlign.CENTER }); + } + } else { + emptyIcon = new St.Icon({ icon_name: INDICATOR_ICON, style_class: 'system-status-icon clipboard-indicator-icon', x_align: Clutter.ActorAlign.CENTER }); + } + this.emptyStateSection.add_child(emptyIcon); this.emptyStateSection.add_child(new St.Label({ text: _('Clipboard is empty'), x_align: Clutter.ActorAlign.CENTER @@ -464,6 +535,7 @@ const ClipboardIndicator = GObject.registerClass({ _setEntryLabel (menuItem) { const { entry } = menuItem; if (entry.isText()) { + menuItem.label.set_text(this._truncate(entry.getStringValue(), MAX_ENTRY_LENGTH)); } else if (entry.isImage()) { @@ -497,6 +569,7 @@ const ClipboardIndicator = GObject.registerClass({ // when focus is at the last element of the displayed list let beforeMenuItem = this.clipItemsRadioGroup[currentIndex + 1]; if(beforeMenuItem.actor.visible){ + return beforeMenuItem; } @@ -633,6 +706,7 @@ const ClipboardIndicator = GObject.registerClass({ _favoriteToggle (menuItem) { menuItem.entry.favorite = menuItem.entry.isFavorite() ? false : true; + this._moveItemFirst(menuItem); this._updateCache(); this.#showElements(); @@ -885,7 +959,6 @@ const ClipboardIndicator = GObject.registerClass({ this._onHistoryIntervalClearSettingsChanged.bind(this) ); - if (!CLEAR_HISTORY_ON_INTERVAL) { this._updateIntervalTimer(); @@ -904,6 +977,7 @@ const ClipboardIndicator = GObject.registerClass({ else { //timer already set, but not expired const timeoutMs = (NEXT_HISTORY_CLEAR - currentTime) * 1000; this._historyClearTimeoutId = setTimeout(() => { + this._clearHistory(true); this._scheduleNextHistoryClear(); }, timeoutMs); @@ -988,6 +1062,7 @@ const ClipboardIndicator = GObject.registerClass({ let hours = Math.floor(timeLeft / 3600); let minutes = Math.floor((timeLeft % 3600) / 60); + let seconds = Math.floor(timeLeft % 60); let formattedTime = ''; @@ -1190,6 +1265,7 @@ const ClipboardIndicator = GObject.registerClass({ this._bindShortcut(PrefsFields.BINDING_PREV_ENTRY, this._previousEntry); this._bindShortcut(PrefsFields.BINDING_NEXT_ENTRY, this._nextEntry); this._bindShortcut(PrefsFields.BINDING_TOGGLE_MENU, this._toggleMenu); + this._bindShortcut(PrefsFields.BINDING_PRIVATE_MODE, this.togglePrivateMode); } @@ -1305,6 +1381,7 @@ const ClipboardIndicator = GObject.registerClass({ if(NOTIFY_ON_CYCLE) { that._showNotification(index + ' / ' + menuItems.length + ': ' + menuItems[i].entry.getStringValue()); + } if (MOVE_ITEM_FIRST) { that._selectEntryWithDelay(menuItems[i]); @@ -1388,7 +1465,6 @@ const ClipboardIndicator = GObject.registerClass({ #clearClipboard () { this.extension.clipboard.set_text(CLIPBOARD_TYPE, ""); - this.icon.icon_name = INDICATOR_ICON; this.#updateIndicatorContent(null); } @@ -1397,6 +1473,7 @@ const ClipboardIndicator = GObject.registerClass({ this.#updateIndicatorContent(entry); } + async #getClipboardContent () { const mimetypes = [ "text/plain;charset=utf-8", @@ -1444,4 +1521,4 @@ const ClipboardIndicator = GObject.registerClass({ return null; } -}); \ No newline at end of file +}); diff --git a/icons/edit-pasted-symbolic.svg b/icons/Adwaita/edit-pasted-symbolic.svg similarity index 100% rename from icons/edit-pasted-symbolic.svg rename to icons/Adwaita/edit-pasted-symbolic.svg diff --git a/icons/edit-pasted-symbolic.svg.license b/icons/Adwaita/edit-pasted-symbolic.svg.license similarity index 100% rename from icons/edit-pasted-symbolic.svg.license rename to icons/Adwaita/edit-pasted-symbolic.svg.license diff --git a/icons/Flat/edit-pasted-symbolic.svg b/icons/Flat/edit-pasted-symbolic.svg new file mode 100644 index 00000000..1e96d961 --- /dev/null +++ b/icons/Flat/edit-pasted-symbolic.svg @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/icons/Flat/edit-pasted-symbolic.svg.license b/icons/Flat/edit-pasted-symbolic.svg.license new file mode 100644 index 00000000..3a52cd55 --- /dev/null +++ b/icons/Flat/edit-pasted-symbolic.svg.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2010-2023 The GNOME Project +SPDX-FileCopyrightText: 2026 IlCraccatore2011 +SPDX-License-Identifier: LGPL-3.0-or-later diff --git a/icons/Yaru/edit-pasted-symbolic.svg b/icons/Yaru/edit-pasted-symbolic.svg new file mode 100644 index 00000000..8915e05e --- /dev/null +++ b/icons/Yaru/edit-pasted-symbolic.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/Yaru/edit-pasted-symbolic.svg.license b/icons/Yaru/edit-pasted-symbolic.svg.license new file mode 100644 index 00000000..3a52cd55 --- /dev/null +++ b/icons/Yaru/edit-pasted-symbolic.svg.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2010-2023 The GNOME Project +SPDX-FileCopyrightText: 2026 IlCraccatore2011 +SPDX-License-Identifier: LGPL-3.0-or-later