.dotfiles/.local/share/gnome-shell/extensions/clipboard-indicator@tudmotu.../extension.js

1135 lines
39 KiB
JavaScript
Raw Normal View History

import Clutter from 'gi://Clutter';
import GObject from 'gi://GObject';
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';
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 CONFIRM_ON_CLEAR = true;
let MAX_TOPBAR_LENGTH = 15;
let TOPBAR_DISPLAY_MODE = 1; //0 - only icon, 1 - only clipboard content, 2 - both
let DISABLE_DOWN_ARROW = false;
let STRIP_TEXT = false;
let KEEP_SELECTED_ON_CLEAR = false;
let PASTE_BUTTON = true;
let PINNED_ON_BOTTOM = 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;
}
}
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(this._downArrow);
this.add_child(hbox);
this._createHistoryLabel();
this._loadSettings();
this.dialogManager = new DialogManager();
this._buildMenu().then(() => {
this._updateTopbarLayout();
this._setupListener();
});
}
#updateIndicatorContent(entry) {
if (this.preventIndicatorUpdate || (TOPBAR_DISPLAY_MODE !== 1 && TOPBAR_DISPLAY_MODE !== 2)) {
return;
}
if (!entry || PRIVATEMODE) {
this._buttonImgPreview.destroy_all_children();
this._buttonText.set_text("...")
} else {
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: '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(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_actor(that.favoritesSection.actor);
that.scrollViewFavoritesMenuSection.actor.add_actor(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_actor(that.historySection.actor);
that.scrollViewMenuSection.actor.add_actor(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) => {
if(event.get_key_symbol() === Clutter.KEY_Delete) {
this.#selectNextMenuItem(menuItem);
this._removeEntry(menuItem, 'delete');
}
else if (event.get_key_symbol() === Clutter.KEY_p) {
this.#selectNextMenuItem(menuItem);
this._favoriteToggle(menuItem);
}
else if (event.get_key_symbol() === Clutter.KEY_v) {
this.#pasteItem(menuItem);
}
})
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.
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('ClipboardIndicator',
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(this._notifSource, message);
}
else {
notification = this._notifSource.notifications[0];
notification.update(message, '', { clear: true });
}
if (typeof transformFn === 'function') {
transformFn(notification);
}
notification.setTransient(true);
this._notifSource.showNotification(notification);
}
_createHistoryLabel () {
this._historyLabel = new St.Label({
style_class: 'ci-notification-label',
text: ''
});
global.stage.add_actor(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);
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);
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);
}
async _onSettingsChange () {
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();
}
_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;
}
if(TOPBAR_DISPLAY_MODE === 1){
this.icon.visible = false;
this._buttonText.visible = true;
}
if(TOPBAR_DISPLAY_MODE === 2){
this.icon.visible = true;
this._buttonText.visible = true;
}
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
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
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.#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',
'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;
}
const entry = new ClipboardEntry(type, bytes.get_data(), false);
if (entry.isImage()) {
this.registry.writeEntryFile(entry);
}
resolve(entry);
}));
if (result) return result;
}
return null;
}
});