diff --git a/clipboard-history@axaul/README.md b/clipboard-history@axaul/README.md new file mode 100644 index 00000000000..02453e4d4d9 --- /dev/null +++ b/clipboard-history@axaul/README.md @@ -0,0 +1,54 @@ +[English Version] | [Version Française](#historique-du-presse-papiers) + +# Clipboard History + +**Clipboard History** is an applet for the Cinnamon desktop environment. It allows you to view, manage and reuse the most recent items copied to your clipboard. + +# Usage +- **Click the applet icon** to open the menu. +- **Select an item** from the list to copy it back to the clipboard. +- **Clear** the history using the dedicated button. +- **Enable debug mode** to see logs via `Alt + F2`, then type `lg`. +- **Configure a keyboard shortcut** for quicker access. + +# Available Settings +- **Enable debug mode**: Shows log information in the system journal. +- **Maximum number of item stored**: Defines the clipboard history size. +- **Clipboard check interval** (in seconds): Sets how often the clipboard is monitored. +- **Open clipboard history**: Configure a custom keyboard shortcut to open the applet. + +# Limitations +- A short check interval may slightly affect system performance. +- History is cleared each time Cinnamon restarts (no persistence). +- Only plain text is supported (no images or files). + +# About the applet +The original project was created by [Axel GALLIC (alias Axaul)](https://github.com/GALLIC-A) on his personal GitHub repository. + +--- + +[Version Française] | [English Version](#clipboard-history) + +# Historique du presse-papiers +**Historique du presse-papiers** est un applet développé pour Cinnamon et permet de consulter et réutiliser les derniers éléments copiés dans votre presse-papiers. + +# Utilisation +- **Cliquez sur l'icône** de l'applet pour ouvrir le menu. +- **Sélectionnez un élément** pour le copier à nouveau dans votre presse-papiers. +- **Videz l'historique** avec le bouton prévu à cet effet. +- **Activez le mode de débogage** pour voir les journaux via `Alt + F2`, puis `lg`. +- **Configurez un raccourci clavier** pour accéder plus rapidement à l'applet. + +# Paramètres +- **Activer le mode de débogage** : Affiche les informations de log dans les journaux système. +- **Nombre maximal d'éléments enregistrés** : Définit la taille maximale de l'historique. +- **Intervalle de vérification du presse-papiers** (en secondes) : Ajuste la fréquence de surveillance. +- **Ouvrir l'historique du presse-papiers** : Permet de configurer un raccourci clavier pour ouvrir l'applet. + +# Limitations +- Une fréquence de vérification trop élevée du presse-papiers peut impacter légèrement les performances. +- L'historique est réinitialisé après un redémarrage de Cinnamon. +- Seuls les contenus texte sont gérés. + +# À propos +Le projet original a été créé par [Axel GALLIC (alias Axaul)](https://github.com/GALLIC-A) sur son dépôt GitHub personnel. \ No newline at end of file diff --git a/clipboard-history@axaul/files/clipboard-history@axaul/applet.js b/clipboard-history@axaul/files/clipboard-history@axaul/applet.js new file mode 100644 index 00000000000..c533c1da6fd --- /dev/null +++ b/clipboard-history@axaul/files/clipboard-history@axaul/applet.js @@ -0,0 +1,241 @@ +// Cinnamon +const Applet = imports.ui.applet; +const PopupMenu = imports.ui.popupMenu; +const Main = imports.ui.main; +const St = imports.gi.St; +const Mainloop = imports.mainloop; +const Settings = imports.ui.settings; +const Gettext = imports.gettext; +const GLib = imports.gi.GLib; + +// Applet params +const UUID = "clipboard-history@axaul"; +Gettext.bindtextdomain(UUID, GLib.get_home_dir() + "/.local/share/locale"); +const _ = Gettext.domain(UUID).gettext; +const AppletDir = imports.ui.appletManager.appletMeta[UUID].path; +const CONTENT_MAX_SIZE_TO_DISPLAY = 100; + +function HistoriquePressePapiers(metadata, orientation, panelHeight, instanceId) { + this._init(metadata, orientation, panelHeight, instanceId); +} + +HistoriquePressePapiers.prototype = { + __proto__: Applet.IconApplet.prototype, + + _init: function(metadata, orientation, panelHeight, instanceId) { + Applet.IconApplet.prototype._init.call(this, orientation, panelHeight, instanceId); + + // ***************** + // ** Init params ** + // ***************** + + this._preferences = this._getDefaultSettings(); + this.settings = new Settings.AppletSettings(this._preferences, UUID, instanceId); + this._bindSettings(); + + // ******************** + // ** Init applet UI ** + // ******************** + + this.set_applet_icon_path(AppletDir + '/icon.png'); + this.set_applet_tooltip(_("Open clipboard history")); + + // Main menu + this.menuManager = new PopupMenu.PopupMenuManager(this); + this.menu = new Applet.AppletPopupMenu(this, orientation); + this.menuManager.addMenu(this.menu); + + // Init & add static elements to the menu + this._addStaticMenuItems(); + + // Init dynamic section (clipboard history content) + this.sectionHistorique = new PopupMenu.PopupMenuSection(); + this.menu.addMenuItem(this.sectionHistorique); + + // ************************ + // ** Init default datas ** + // ************************ + this.historiquePressePapiers = []; + this.menuItems = []; + this.timeoutId = null; + + // ********************* + // ** Applet starting ** + // ********************* + this._reloadHistorique(); + this._startClipboardWatcher(); + }, + + on_applet_clicked: function() { + this.menu.toggle(); + }, + + // *********************** + // ** MANAGE PARAMETERS ** + // *********************** + _getDefaultSettings: function() { + return { + debug_mode: false, + clipboard_history_limit: 15, + poll_interval: 5, + open_applet_shortcut: "v" + }; + }, + + _bindSettings: function() { + this.settings.bindProperty( + Settings.BindingDirection.IN, + "debug_mode", // settings-schema.json key + "debug_mode", // _preferences properties + this._onSettingsChanged.bind(this), // callback + null + ); + this.settings.bindProperty(Settings.BindingDirection.IN, "clipboard_history_limit", "clipboard_history_limit", this._onSettingsChanged.bind(this), null); + this.settings.bindProperty(Settings.BindingDirection.IN, "poll_interval", "poll_interval", this._onSettingsChanged.bind(this), null); + this.settings.bindProperty(Settings.BindingDirection.IN, "open_applet_shortcut", "open_applet_shortcut", () => { + Main.keybindingManager.addHotKey( + UUID, + this._preferences.open_applet_shortcut, + () => this.menu.toggle() + ); + }, null); + }, + + _onSettingsChanged: function() { + this._logDebug(_("[SETTINGS] Settings change detected")); + this._restartClipboardWatcher(); + }, + + // *************** + // ** MENU & UI ** + // *************** + _addStaticMenuItems: function() { + this.boutonEffacerTout = new PopupMenu.PopupMenuItem(_("🗑️ Clear entire history")); + this.boutonEffacerTout.connect('activate', () => { + this._clearHistorique(); + Main.notify(_("History emptied and clipboard erased!")); + }); + this.menu.addMenuItem(this.boutonEffacerTout); + + // Separate buttons from menu + this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); + }, + + _reloadHistorique: function() { + this.sectionHistorique.removeAll(); + this.menuItems = []; + this.historiquePressePapiers.forEach(contenu => { + let item = this._createClipboardMenuItem(contenu); + this.sectionHistorique.addMenuItem(item); + this.menuItems.unshift(item); + }); + }, + + _createClipboardMenuItem: function(contenu) { + + let ligneContenu = contenu.split("\n").map(line => line.trim()).join(" "); + ligneContenu = ligneContenu.trim(); + + let contenuAffiche = ligneContenu.length > CONTENT_MAX_SIZE_TO_DISPLAY + ? ligneContenu.substring(0, CONTENT_MAX_SIZE_TO_DISPLAY - 3) + "..." + : ligneContenu; + let item = new PopupMenu.PopupMenuItem(contenuAffiche); + item.connect('activate', () => { + this._copyToClipboard(contenu); + Main.notify(_("“%s” copied to clipboard.").format(contenuAffiche)); + }); + return item; + }, + + // ******************** + // ** MANAGE HISTORY ** + // ******************** + _clearHistorique: function() { + this._logDebug(_("Clipboard has been emptied.")); + this.historiquePressePapiers = []; + this._reloadHistorique(); + + // When we clear history, we erase the current content of the clipboard + let pressePapiers = St.Clipboard.get_default(); + pressePapiers.get_text(St.ClipboardType.CLIPBOARD, (clip, contenu) => { + if (contenu !== null && contenu !== undefined) { + this._copyToClipboard(""); + this._logDebug(_("Clipboard cleared (it contained text).")); + } else { + this._logDebug(_("Clipboard not cleared (non-text content).")); + } + }); + }, + + _addToHistorique: function(contenu) { + try { + if(this.historiquePressePapiers.length >= this._preferences.clipboard_history_limit) { + this.historiquePressePapiers.pop(); + } + this.historiquePressePapiers.unshift(contenu); + this._reloadHistorique(); + } catch(ex) { + global.logError(_("An error occurred while adding content to clipboard history: %s").format(ex)); + } + }, + + _moveToTopHistorique: function(contenu) { + // The purpose of this function is to move the selected element to be copied to the top of the history list. + // First, we delete the existing occurrence, then we re-insert it at the top of the list, and finally we reload history. + this.historiquePressePapiers = this.historiquePressePapiers.filter(c => c !== contenu); + this.historiquePressePapiers.unshift(contenu); + this._reloadHistorique(); + }, + + _handleClipboardContent: function(contenu) { + if(!contenu || contenu.trim() === "") return; + if(this.historiquePressePapiers.includes(contenu)) { + this._moveToTopHistorique(contenu); + } else { + this._addToHistorique(contenu); + } + }, + + // **************** + // ** MONITORING ** + // **************** + _startClipboardWatcher: function() { + let dernierContenu = ""; + const verifierPressePapiers = () => { + let pressePapiers = St.Clipboard.get_default(); + pressePapiers.get_text(St.ClipboardType.CLIPBOARD, (clip, contenu) => { + if(contenu && contenu !== dernierContenu) { + this._logDebug(_("New content detected: “%s”").format(contenu)); + dernierContenu = contenu; + this._handleClipboardContent(contenu); + } else { + this._logDebug(_("The contents of the clipboard have not changed since last %s seconds.").format(this._preferences.poll_interval)); + } + }); + return true; // without it, the loop doesn't work + } + this._logDebug(_("Starting clipboard checking.")); + this._timeoutId = Mainloop.timeout_add_seconds(this._preferences.poll_interval, verifierPressePapiers); + }, + + _restartClipboardWatcher: function() { + if (this._timeoutId) { + Mainloop.source_remove(this._timeoutId); + this._timeoutId = null; + } + this._startClipboardWatcher(); + }, + + _copyToClipboard: function(contenu) { + let pressePapiers = St.Clipboard.get_default(); + pressePapiers.set_text(St.ClipboardType.CLIPBOARD, contenu); + }, + + _logDebug: function(msg){ + if (this._preferences.debug_mode) global.log(`[DEBUG] ${msg}`); + } +}; + +function main(metadata, orientation, panelHeight, instanceId) { + return new HistoriquePressePapiers(metadata, orientation, panelHeight, instanceId); +} diff --git a/clipboard-history@axaul/files/clipboard-history@axaul/icon.png b/clipboard-history@axaul/files/clipboard-history@axaul/icon.png new file mode 100644 index 00000000000..e0bad1d5a6b Binary files /dev/null and b/clipboard-history@axaul/files/clipboard-history@axaul/icon.png differ diff --git a/clipboard-history@axaul/files/clipboard-history@axaul/metadata.json b/clipboard-history@axaul/files/clipboard-history@axaul/metadata.json new file mode 100644 index 00000000000..62cadf091c4 --- /dev/null +++ b/clipboard-history@axaul/files/clipboard-history@axaul/metadata.json @@ -0,0 +1,5 @@ +{ + "uuid": "clipboard-history@axaul", + "name": "Clipboard history", + "description": "Utility to access previously copied text." +} \ No newline at end of file diff --git a/clipboard-history@axaul/files/clipboard-history@axaul/po/clipboard-history@axaul.pot b/clipboard-history@axaul/files/clipboard-history@axaul/po/clipboard-history@axaul.pot new file mode 100644 index 00000000000..e0e9b688f91 --- /dev/null +++ b/clipboard-history@axaul/files/clipboard-history@axaul/po/clipboard-history@axaul.pot @@ -0,0 +1,102 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-07 19:07+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: applet.js:43 +msgid "Open clipboard history" +msgstr "" + +#: applet.js:107 +msgid "[SETTINGS] Settings change detected" +msgstr "" + +#: applet.js:115 +msgid "🗑️ Clear entire history" +msgstr "" + +#: applet.js:118 +msgid "History emptied and clipboard erased!" +msgstr "" + +#: applet.js:143 +#, javascript-format +msgid "“%s” copied to clipboard." +msgstr "" + +#: applet.js:152 +msgid "Clipboard has been emptied." +msgstr "" + +#: applet.js:161 +msgid "Clipboard cleared (it contained text)." +msgstr "" + +#: applet.js:163 +msgid "Clipboard not cleared (non-text content)." +msgstr "" + +#: applet.js:176 +#, javascript-format +msgid "An error occurred while adding content to clipboard history: %s" +msgstr "" + +#: applet.js:206 +#, javascript-format +msgid "New content detected: “%s”" +msgstr "" + +#: applet.js:210 +#, javascript-format +msgid "The contents of the clipboard have not changed since last %s seconds." +msgstr "" + +#: applet.js:215 +msgid "Starting clipboard checking." +msgstr "" + +#: clipboard-history@axaul->metadata.json->name +msgid "Clipboard history" +msgstr "" + +#: clipboard-history@axaul->metadata.json->description +msgid "Utility to access previously copied text." +msgstr "" + +#: clipboard-history@axaul->settings-schema.json->debug_mode->description +msgid "Enable debug mode" +msgstr "" + +#: clipboard-history@axaul->settings-schema.json->clipboard_history_limit->description +msgid "Maximum number of items that can be stored in the clipboard history." +msgstr "" + +#: clipboard-history@axaul->settings-schema.json->poll_interval->description +msgid "Clipboard check interval" +msgstr "" + +#: clipboard-history@axaul->settings-schema.json->poll_interval->units +msgid "seconds" +msgstr "" + +#: clipboard-history@axaul->settings-schema.json->open_applet_shortcut->description +msgid "Open clipboard history with shortcut." +msgstr "" + +#: clipboard-history@axaul->settings-schema.json->open_applet_shortcut->tooltip +msgid "Save keyboard shortcuts to open the clipboard history." +msgstr "" diff --git a/clipboard-history@axaul/files/clipboard-history@axaul/po/fr.po b/clipboard-history@axaul/files/clipboard-history@axaul/po/fr.po new file mode 100644 index 00000000000..ea3b52e167a --- /dev/null +++ b/clipboard-history@axaul/files/clipboard-history@axaul/po/fr.po @@ -0,0 +1,103 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-05-07 19:07+0200\n" +"PO-Revision-Date: 2025-05-07 19:11+0200\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.5\n" + +#: applet.js:43 +msgid "Open clipboard history" +msgstr "Ouvrir l'historique du presse-papiers" + +#: applet.js:107 +msgid "[SETTINGS] Settings change detected" +msgstr "[PARAMS] Changement de paramètres détecté" + +#: applet.js:115 +msgid "🗑️ Clear entire history" +msgstr "🗑️ Vider tout l'historique" + +#: applet.js:118 +msgid "History emptied and clipboard erased!" +msgstr "Historique vidé et presse-papiers effacé !" + +#: applet.js:143 +#, javascript-format +msgid "“%s” copied to clipboard." +msgstr "« %s » copié dans le presse-papiers." + +#: applet.js:152 +msgid "Clipboard has been emptied." +msgstr "Le presse-papiers a été vidé." + +#: applet.js:161 +msgid "Clipboard cleared (it contained text)." +msgstr "Presse-papiers vidé (contenait du texte)." + +#: applet.js:163 +msgid "Clipboard not cleared (non-text content)." +msgstr "Presse-papiers non vidé (contenu non texte)." + +#: applet.js:176 +#, javascript-format +msgid "An error occurred while adding content to clipboard history: %s" +msgstr "Une erreur est survenue lors de l'ajout du contenu à l'historique du presse-papiers : %s\"" + +#: applet.js:206 +#, javascript-format +msgid "New content detected: “%s”" +msgstr "Nouveau contenu détecté : « %s »" + +#: applet.js:210 +#, javascript-format +msgid "The contents of the clipboard have not changed since last %s seconds." +msgstr "Le contenu du presse-papiers n'a pas changé depuis ces %s dernières secondes." + +#: applet.js:215 +msgid "Starting clipboard checking." +msgstr "Démarrage de la surveillance du presse-papiers." + +#: clipboard-history@axaul->metadata.json->name +msgid "Clipboard history" +msgstr "Historique du presse-papiers" + +#: clipboard-history@axaul->metadata.json->description +msgid "Utility to access previously copied text." +msgstr "Utilitaire pour accéder à des éléments anciennement copiés." + +#: clipboard-history@axaul->settings-schema.json->debug_mode->description +msgid "Enable debug mode" +msgstr "Activer le mode de débogage" + +#: clipboard-history@axaul->settings-schema.json->clipboard_history_limit->description +msgid "Maximum number of items that can be stored in the clipboard history." +msgstr "Nombre maximal d'éléments pouvant être stockés dans l'historique du presse-papiers." + +#: clipboard-history@axaul->settings-schema.json->poll_interval->description +msgid "Clipboard check interval" +msgstr "Intervalle de vérification du presse-papiers" + +#: clipboard-history@axaul->settings-schema.json->poll_interval->units +msgid "seconds" +msgstr "secondes" + +#: clipboard-history@axaul->settings-schema.json->open_applet_shortcut->description +msgid "Open clipboard history with shortcut." +msgstr "Ouvrir l'historique du presse-papiers à l'aide d'un raccourci clavier." + +#: clipboard-history@axaul->settings-schema.json->open_applet_shortcut->tooltip +msgid "Save keyboard shortcuts to open the clipboard history." +msgstr "Enregistrer des raccourcis-clavier pour ouvrir l'historique du presse-papiers." diff --git a/clipboard-history@axaul/files/clipboard-history@axaul/settings-schema.json b/clipboard-history@axaul/files/clipboard-history@axaul/settings-schema.json new file mode 100644 index 00000000000..e0363ab5733 --- /dev/null +++ b/clipboard-history@axaul/files/clipboard-history@axaul/settings-schema.json @@ -0,0 +1,30 @@ +{ + "debug_mode": { + "type": "switch", + "description": "Enable debug mode", + "default": false + }, + "clipboard_history_limit": { + "type": "spinbutton", + "description": "Maximum number of items that can be stored in the clipboard history.", + "step": 5, + "default": 15, + "min": 1, + "max": 100 + }, + "poll_interval": { + "type": "spinbutton", + "description": "Clipboard check interval", + "units": "seconds", + "step": 1, + "default": 5, + "min": 1, + "max": 100 + }, + "open_applet_shortcut": { + "type": "keybinding", + "description": "Open clipboard history with shortcut.", + "default": "v", + "tooltip": "Save keyboard shortcuts to open the clipboard history." + } +} diff --git a/clipboard-history@axaul/info.json b/clipboard-history@axaul/info.json new file mode 100644 index 00000000000..271057d2a6d --- /dev/null +++ b/clipboard-history@axaul/info.json @@ -0,0 +1 @@ +{"author":"GALLIC-A"} \ No newline at end of file diff --git a/clipboard-history@axaul/screenshot.png b/clipboard-history@axaul/screenshot.png new file mode 100644 index 00000000000..0893ffa047f Binary files /dev/null and b/clipboard-history@axaul/screenshot.png differ