From 95aea608a073ed90164884cea0dae944ae53ffc4 Mon Sep 17 00:00:00 2001
From: fredcw <58893963+fredcw@users.noreply.github.com>
Date: Mon, 3 Mar 2025 03:38:08 +0000
Subject: [PATCH 1/3] Add icon-menu appet
---
.../cinnamon-sass/widgets/_startmenu.scss | 80 +
.../applets/icon-menu@cinnamon.org/applet.js | 1818 ++++
.../icon-menu@cinnamon.org/appsview.js | 568 ++
.../icon-menu@cinnamon.org/categoriesview.js | 418 +
.../icon-menu@cinnamon.org/contextmenu.js | 434 +
.../applets/icon-menu@cinnamon.org/display.js | 317 +
.../applets/icon-menu@cinnamon.org/emoji.js | 8028 +++++++++++++++++
.../icon-menu@cinnamon.org/metadata.json | 7 +
.../settings-schema.json | 369 +
.../applets/icon-menu@cinnamon.org/sidebar.js | 416 +
.../icon-menu@cinnamon.org/stylesheet.css | 54 +
.../applets/icon-menu@cinnamon.org/utils.js | 254 +
12 files changed, 12763 insertions(+)
create mode 100644 files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/applet.js
create mode 100644 files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/appsview.js
create mode 100644 files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/categoriesview.js
create mode 100644 files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/contextmenu.js
create mode 100644 files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/display.js
create mode 100644 files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/emoji.js
create mode 100644 files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/metadata.json
create mode 100755 files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/settings-schema.json
create mode 100644 files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/sidebar.js
create mode 100755 files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/stylesheet.css
create mode 100755 files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/utils.js
diff --git a/data/theme/cinnamon-sass/widgets/_startmenu.scss b/data/theme/cinnamon-sass/widgets/_startmenu.scss
index c2e3cc5a44..0b9beedeff 100644
--- a/data/theme/cinnamon-sass/widgets/_startmenu.scss
+++ b/data/theme/cinnamon-sass/widgets/_startmenu.scss
@@ -94,3 +94,83 @@ $menu_outer_border_radius: $base_border_radius * 1.25;
.menu-search-entry-icon {
icon-size: $scalable_icon_size;
}
+
+// gridmenu (icon-menu)
+
+.menu-category-button:highlighted, // Used to highlight category containing newly installed apps.
+.menu-category-button-selected:highlighted {
+ font-weight: bold;
+}
+
+.menu-applications-header-text { // shows folder name, "no recent files", error messages etc.
+ font-weight: bold;
+ font-size: 110%;
+}
+
+.menu-applications-subheading {
+ font-weight: bold;
+ font-size: 100%;
+ padding-top: 0.3em;
+ padding-left: 0.3em;
+ //text-align: center; - known bug: causes label to jump about when resizing menu.
+}
+
+.menu-applications-grid-box {
+ left-padding: 8px;
+
+ .menu-application-button,
+ .menu-application-button-selected {
+ padding: 10px 2px;
+ }
+
+ .menu-application-button-label {
+ text-align: center;
+ }
+}
+
+.gridmenu-sidebar-box {
+ padding: 0px 6px;
+}
+
+.gridmenu {
+ .menu-applications-inner-box {
+ padding: 0px;
+ }
+
+ .menu-categories-box {
+ padding: 0px 6px;
+ }
+
+ .menu-search-box,
+ .menu-search-box:ltr,
+ .menu-search-box:rtl {
+ min-width: 160px;
+ padding: 0px 8px;
+ }
+
+ &.sidebar-left {
+ .gridmenu-sidebar-box {
+ padding-right: 16px;
+ }
+ }
+
+ &.sidebar-right {
+ .gridmenu-sidebar-box {
+ padding-left: 8px;
+ }
+ }
+
+ &.sidebar-left,
+ &.sidebar-right,
+ &.sidebar-bottom {
+ .gridmenu-middle-pane {
+ padding-bottom: 12px;
+ }
+ }
+
+ &.sidebar-top .gridmenu-middle-pane {
+ padding-top: 12px;
+ }
+}
+
+
diff --git a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/applet.js b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/applet.js
new file mode 100644
index 0000000000..60f3b7467c
--- /dev/null
+++ b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/applet.js
@@ -0,0 +1,1818 @@
+const Gio = imports.gi.Gio;
+const Gtk = imports.gi.Gtk;
+const GLib = imports.gi.GLib;
+const CMenu = imports.gi.CMenu;
+const Clutter = imports.gi.Clutter;
+const Cinnamon = imports.gi.Cinnamon;
+const XApp = imports.gi.XApp;
+const St = imports.gi.St;
+const Meta = imports.gi.Meta;
+const Main = imports.ui.main;
+const Util = imports.misc.util;
+const GnomeSession = imports.misc.gnomeSession;
+const AppletManager = imports.ui.appletManager;
+const {ScreenSaverProxy} = imports.misc.screenSaver;
+const {PopupMenuManager} = imports.ui.popupMenu;
+const {getAppFavorites} = imports.ui.appFavorites;
+const {TextIconApplet, AllowedLayout, AppletPopupMenu, PopupResizeHandler} = imports.ui.applet;
+const {SignalManager} = imports.misc.signalManager;
+const {launch_all} = imports.ui.searchProviderManager;
+const {AppletSettings} = imports.ui.settings;
+const Mainloop = imports.mainloop;
+
+const {graphemeBaseChars, searchStr} = require('./utils');
+const {Display} = require('./display');
+const {EMOJI} = require('./emoji');
+const EMOJI_CODE = 0, EMOJI_NAME = 1, EMOJI_KEYWORDS = 2;
+const REMEMBER_RECENT_KEY = 'remember-recent-files';
+const SEARCH_THRESHOLD = 0.45;
+const SidebarPlacement = Object.freeze({TOP: 0, BOTTOM: 1, LEFT: 2, RIGHT: 3});
+
+/* This graph shows the classes in which other classes are instantiated and how they are
+ * acessed. e.g. to call the update() method in categoriesView from contextMenu class use:
+ * this.appThis.display.categoriesView.update()
+
+ ┌── class AppsView ───────┬── class AppButton
+ │ └── class Subheading
+ │
+ ┌── class Display ────┼── class CategoriesView ──── class CategoryButton
+ │ │
+ │ ├── class Sidebar ────────┬── class SidebarButton
+ │ │ └── class Separator
+class │ │
+CinnamenuApplet ──┼ ├── class ContextMenu ─────── class ContextMenuItem
+ │ │
+ │ └── class SearchView
+ │
+ ├── class Apps
+ │
+ └── class RecentApps
+
+*/
+
+class CinnamenuApplet extends TextIconApplet {
+ constructor(metadata, orientation, panel_height, instance_id) {
+ super(orientation, panel_height, instance_id);
+ this.setAllowedLayout(AllowedLayout.BOTH);
+ this.privacy_settings = new Gio.Settings({schema_id: 'org.cinnamon.desktop.privacy'});
+ this.appFavorites = getAppFavorites();
+ this.currentCategory = 'all';
+ this.recentManagerDefault = Gtk.RecentManager.get_default();
+ this.orientation = orientation;
+ this.menuManager = new PopupMenuManager(this);
+ this.menu = new AppletPopupMenu(this, this.orientation);
+ this.menuManager.addMenu(this.menu);
+ this.signals = new SignalManager(null);
+ this.appSystem = Cinnamon.AppSystem.get_default();
+ this._canUninstallApps = GLib.file_test("/usr/bin/cinnamon-remove-application",
+ GLib.FileTest.EXISTS);
+ this._pamacManagerAvailable = GLib.file_test("/usr/bin/pamac-manager", GLib.FileTest.EXISTS);
+ this.resizer = new PopupResizeHandler(
+ this.menu.actor,
+ () => this.orientation,
+ (w,h) => this.display.onMenuResized(w,h),
+ () => this.settings.customMenuWidth * global.ui_scale,
+ () => this.settings.customMenuHeight * global.ui_scale
+ );
+ this.signals.connect(this.privacy_settings, 'changed::' + REMEMBER_RECENT_KEY,
+ () => this._onEnableRecentsChange());
+
+ const refreshDisplay = () => {
+ // TBD: For some reason the onEnable* settings callbacks get called several times per
+ // settings change. This is causing the start up category to reset, so throttling this
+ // function to 250ms prevents excess invocation.
+ if (!this.lastRenderTime) this.lastRenderTime = 0;
+ const now = Date.now();
+ if ((now - this.lastRenderTime) <= 250) {
+ return;
+ }
+ this.lastRenderTime = now;
+
+ this.display.destroy();
+ this.menu.removeAll();
+ this.display = new Display(this);
+ this.display.clearFocusedActors();
+ }
+
+ this.signals.connect(Main.themeManager, 'theme-set', () => {
+ this._updateIconAndLabel();
+ Mainloop.timeout_add(0, () => {
+ refreshDisplay();
+ return false;
+ });
+ });
+ this.iconTheme = Gtk.IconTheme.get_default();
+ this.signals.connect(this.iconTheme, 'changed', () => this._updateIconAndLabel());
+ this.signals.connect(this.appSystem, 'installed-changed', () => {
+ this.apps.installedChanged();
+ refreshDisplay();
+ });
+ this.signals.connect(this.appFavorites, 'changed', () => {
+ if (this.display) {// Check if display is initialised
+ this.display.sidebar.populate();
+ this.display.updateMenuSize();
+ if (this.currentCategory === 'favorite_apps' && !this.searchActive) {
+ this.setActiveCategory(this.currentCategory);
+ }
+ }
+ });
+ this.signals.connect(
+ this.menu,
+ 'open-state-changed',
+ this._onOpenStateToggled.bind(this)
+ );
+ this.signals.connect(this.menu,
+ 'menu-animated-closed',
+ this._onMenuClosed.bind(this)
+ );
+ this.apps = new Apps(this.appSystem);
+ this.screenSaverProxy = new ScreenSaverProxy();
+ this.sessionManager = new GnomeSession.SessionManager();
+
+ const updateKeybinding = () => {
+ Main.keybindingManager.addHotKey(
+ 'overlay-key-' + this.instance_id,
+ this.settings.overlayKey,
+ () => {
+ if (Main.overview.visible || Main.expo.visible) return;
+ if (!this.isOpen) {
+ this.panel.peekPanel();
+ }
+ this.menu.toggle_with_options(this.settings.enableAnimation);
+ }
+ );
+ };
+
+ const updateActivateOnHover = () => {
+ const openMenu = () => {
+ if (!this._applet_context_menu.isOpen) {
+ this.menu.open(this.settings.enableAnimation);
+ }
+ };
+
+ if (this.signals.isConnected('enter-event', this.actor)) {
+ this.signals.disconnect('enter-event', this.actor);
+ this.signals.disconnect('leave-event', this.actor);
+ }
+ if (this.settings.activateOnHover) {
+ this.signals.connect(this.actor, 'enter-event', () => {
+ if (!this.menu.isOpen && !this.openMenuTimeoutId) {
+ this.openMenuTimeoutId = Mainloop.timeout_add(this.settings.hoverDelayMs, () => openMenu());
+ }
+ });
+ this.signals.connect(this.actor, 'leave-event', () => {
+ if (this.openMenuTimeoutId) {
+ Mainloop.source_remove(this.openMenuTimeoutId);
+ this.openMenuTimeoutId = null;
+ }
+ });
+ }
+ };
+
+ this.settings = {};
+ this.appletSettings = new AppletSettings(this.settings, __meta.uuid, this.instance_id);
+ [
+ { key: 'categories', value: 'categories', cb: null },
+ { key: 'custom-menu-height', value: 'customMenuHeight', cb: null },
+ { key: 'custom-menu-width', value: 'customMenuWidth', cb: null },
+ { key: 'recent-apps', value: 'recentApps', cb: null },
+ { key: 'folder-categories', value: 'folderCategories', cb: null },
+
+ { key: 'description-placement', value: 'descriptionPlacement', cb: refreshDisplay },
+ { key: 'show-sidebar', value: 'showSidebar', cb: refreshDisplay},
+ { key: 'sidebar-placement', value: 'sidebarPlacement', cb: refreshDisplay },
+ { key: 'sidebar-favorites', value: 'sidebarFavorites', cb: refreshDisplay },
+
+ { key: 'show-categories', value: 'showCategories', cb: refreshDisplay},
+ { key: 'show-places-category', value: 'showPlaces', cb: null},
+ { key: 'show-recents-category', value: 'showRecents', cb: this._onEnableRecentsChange },
+ { key: 'show-favorite-apps-category', value: 'showFavAppsCategory', cb: null },
+ { key: 'show-home-folder-category', value: 'showHomeFolder', cb: this._onShowHomeFolderChange},
+
+ { key: 'overlay-key', value: 'overlayKey', cb: updateKeybinding },
+ { key: 'activate-on-hover', value: 'activateOnHover', cb: updateActivateOnHover },
+ { key: 'hover-delay', value: 'hoverDelayMs', cb: updateActivateOnHover },
+ { key: 'enable-animation', value: 'enableAnimation', cb: null },
+ { key: 'open-on-category', value: 'openOnCategory', cb: null },
+
+ { key: 'category-click', value: 'categoryClick', cb: null },
+ { key: 'enable-autoscroll', value: 'enableAutoScroll', cb: refreshDisplay },
+ { key: 'show-hidden-files', value: 'showHiddenFiles', cb: null },
+
+ { key: 'enable-emoji-search', value: 'enableEmojiSearch', cb: null },
+ { key: 'enable-home-folder-search', value: 'searchHomeFolder', cb: null },
+
+ { key: 'menu-icon-custom', value: 'menuIconCustom', cb: this._updateIconAndLabel },
+ { key: 'menu-icon', value: 'menuIcon', cb: this._updateIconAndLabel },
+ { key: 'menu-icon-size-custom', value: 'menuIconSizeCustom', cb: this._updateIconAndLabel },
+ { key: 'menu-icon-size', value: 'menuIconSize', cb: this._updateIconAndLabel },
+ { key: 'menu-label', value: 'menuLabel', cb: this._updateIconAndLabel },
+
+ { key: 'category-icon-size', value: 'categoryIconSize', cb: refreshDisplay },
+ { key: 'apps-grid-icon-size', value: 'appsGridIconSize', cb: refreshDisplay },
+ { key: 'sidebar-icon-size', value: 'sidebarIconSize', cb: refreshDisplay }
+ ].forEach(setting => this.appletSettings.bind(
+ setting.key,
+ setting.value,
+ setting.cb ? setting.cb.bind(this) : null )
+ );
+
+ this.recentApps = new RecentApps(this);
+ this._onEnableRecentsChange();
+ updateActivateOnHover();
+ updateKeybinding();
+ this.display = new Display(this);
+ this._updateIconAndLabel();
+ }
+//----------------TextIconApplet callbacks----------------
+ on_orientation_changed(orientation) {
+ this.orientation = orientation;
+ if (this.orientation === St.Side.LEFT || this.orientation === St.Side.RIGHT) {
+ this.hide_applet_label(true);
+ } else {
+ this.hide_applet_label(false);
+ }
+ this._updateIconAndLabel();
+ }
+
+ on_applet_added_to_panel() {
+ this._onShowHomeFolderChange();
+ }
+
+ on_applet_removed_from_panel() {
+ Main.keybindingManager.removeHotKey('overlay-key-' + this.instance_id);
+ if (!this.appletSettings) {
+ return;
+ }
+ this.appletSettings.finalize();
+ this.signals.disconnectAllSignals();
+ this.display.destroy();
+ this.menu.destroy();
+ }
+
+ on_applet_clicked() {
+ this.menu.toggle_with_options(this.settings.enableAnimation);
+ }
+
+ _setStyle() {
+ // Override js/applet.js so _updateIconAndLabel doesn't have to fight with size changes
+ // from the panel configuration. This gets called any time set_applet_icon() variants are
+ // called.
+
+ let icon_type = this._applet_icon.get_icon_type();
+ let size;
+
+ if (this.settings.menuIconSizeCustom) {
+ size = Math.max(Math.min(this.settings.menuIconSize, this.panel.height), 1);
+ } else {
+ size = this.getPanelIconSize(icon_type);
+ }
+
+ if (icon_type === St.IconType.FULLCOLOR) {
+ this._applet_icon.set_style_class_name('applet-icon');
+ } else {
+ this._applet_icon.set_style_class_name('system-status-icon');
+ }
+
+ this._applet_icon.set_icon_size(size);
+ }
+//------------settings callbacks-------------
+ launchEditor() {
+ Util.spawnCommandLine('cinnamon-menu-editor');
+ }
+
+ _onEnableRecentsChange () {
+ const recentFilesEnabled = this.privacy_settings.get_boolean(REMEMBER_RECENT_KEY);
+ this.recentsEnabled = this.settings.showRecents && recentFilesEnabled;
+ };
+
+ _updateIconAndLabel() {
+ try {
+ if (this.settings.menuIconCustom) {
+ if (this.settings.menuIcon === '') {
+ this.set_applet_icon_name('');
+ } else if (GLib.path_is_absolute(this.settings.menuIcon) &&
+ GLib.file_test(this.settings.menuIcon, GLib.FileTest.EXISTS)) {
+ if (this.settings.menuIcon.includes('-symbolic')) {
+ this.set_applet_icon_symbolic_path(this.settings.menuIcon);
+ } else {
+ this.set_applet_icon_path(this.settings.menuIcon);
+ }
+ } else if (this.iconTheme.has_icon(this.settings.menuIcon)) {
+ if (this.settings.menuIcon.includes('-symbolic')) {
+ this.set_applet_icon_symbolic_name(this.settings.menuIcon);
+ } else {
+ this.set_applet_icon_name(this.settings.menuIcon);
+ }
+ }
+ } else {
+ const icon_name = global.settings.get_string('app-menu-icon-name');
+ if (icon_name.search("-symbolic") != -1) {
+ this.set_applet_icon_symbolic_name(icon_name);
+ }
+ else {
+ this.set_applet_icon_name(icon_name);
+ }
+ }
+ } catch(e) {
+ global.logWarning('Gridmenu: Could not load icon file ' + this.settings.menuIcon +
+ ' for menu button');
+ }
+ if (this.settings.menuIconCustom && this.settings.menuIcon === '' ||
+ this.settings.menuIconSizeCustom && this.settings.menuIconSize === 0) {
+ this._applet_icon_box.hide();
+ } else {
+ this._applet_icon_box.show();
+ }
+
+ if (this.orientation === St.Side.LEFT || this.orientation === St.Side.RIGHT) {
+ this.set_applet_label('');
+ } else {
+ if (!this.settings.menuLabel) {
+ this.settings.menuLabel = '';
+ }
+ const menuLabel = this.settings.menuLabel.substring(0, 45);
+ this.set_applet_label(menuLabel);
+ this.set_applet_tooltip(menuLabel);
+ }
+ }
+
+ _onShowHomeFolderChange() {
+ const homePath = GLib.get_home_dir();
+ if (this.settings.showHomeFolder) {
+ if (!this.getIsFolderCategory(homePath)) {
+ this.addFolderCategory(homePath);
+ }
+ } else {
+ if (this.getIsFolderCategory(homePath)) {
+ this.removeFolderCategory(homePath);
+ }
+ }
+ }
+//==================================================================
+ addFavoriteAppToPos(add_id, pos_id) {
+ const pos = this.appFavorites._getIds().indexOf(pos_id);
+ if (pos >= 0) { //move
+ this.appFavorites.moveFavoriteToPos(add_id, pos);
+ } else {
+ this.appFavorites.addFavoriteAtPos(add_id, pos);
+ }
+ }
+
+ updateAfterXappFavoriteFileChange() {
+ this.display.sidebar.populate();
+ this.display.categoriesView.update();//in case fav files category needs adding/removing
+ this.display.updateMenuSize();
+ if (this.currentCategory === 'favorite_files') {
+ this.setActiveCategory(this.currentCategory);
+ }
+ }
+
+ xappGetIsFavoriteFile(uri) {
+ const favs = XApp.Favorites.get_default();
+ return favs.find_by_uri(uri) !== null;
+ }
+
+ xappAddFavoriteFile(uri) {
+ const favs = XApp.Favorites.get_default();
+ favs.add(uri);
+ //xapp favs list doesn't update synchronously after adding fav so add small
+ //delay before updating menu.
+ Mainloop.timeout_add(100, this.updateAfterXappFavoriteFileChange.bind(this));
+ }
+
+ xappRemoveFavoriteFile(uri) {
+ const favs = XApp.Favorites.get_default();
+ favs.remove(uri);
+ this.updateAfterXappFavoriteFileChange();
+ }
+
+ getIsFolderCategory(path) {
+ const index = this.settings.folderCategories.indexOf(path);
+ return index > -1;
+ }
+
+ addFolderCategory(path) {
+ const folderCategories = this.settings.folderCategories.slice();
+ folderCategories.push(path);
+ this.settings.folderCategories = folderCategories;
+ }
+
+ removeFolderCategory(path) {
+ const folderCategories = this.settings.folderCategories.slice();
+ const index = folderCategories.indexOf(path);
+ if (index != -1) {
+ folderCategories.splice(index, 1);
+ }
+ this.settings.folderCategories = folderCategories;
+ }
+
+ _onOpenStateToggled(menu, open) {
+ if (global.settings.get_boolean('panel-edit-mode')) {
+ return false;
+ }
+ if (!open) {
+ return true; // this._onMenuClosed() is called on 'menu-animated-closed' signal to handle closing.
+ }
+
+ if (this.openMenuTimeoutId) {
+ Mainloop.source_remove(this.openMenuTimeoutId);
+ this.openMenuTimeoutId = null;
+ }
+
+ this.display.categoriesView.update();//in case menu editor or enabled category changes.
+ this.display.sidebar.populate();//in case fav files changed
+ this.display.sidebar.scrollToQuitButton();//ensure quit button is visible
+
+ global.stage.set_key_focus(this.display.searchView.searchEntry);
+ if (this.currentCategory === 'places' && !this.settings.showPlaces ||
+ this.currentCategory === 'recents' && !this.recentsEnabled ||
+ this.currentCategory === 'favorite_apps' && !this.settings.showFavAppsCategory) {
+ this.currentCategory = 'all';
+ }
+ let openOnCategory = this.currentCategory;
+ if (this.settings.openOnCategory === 4 || !this.settings.showCategories) {
+ openOnCategory = 'all';
+ } else if (this.settings.openOnCategory === 1 && this.settings.showFavAppsCategory) {
+ openOnCategory = 'favorite_apps';
+ } else if (this.settings.openOnCategory === 2 && this.recentsEnabled) {
+ openOnCategory = 'recents';
+ } else if (this.settings.openOnCategory === 3 && this.settings.showPlaces) {
+ openOnCategory = 'places';
+ }
+
+ if (!this.resizer._size_restricted) {
+ this.display.updateMenuSize();
+ }
+ this.setActiveCategory(openOnCategory);
+
+ //Show panel when auto hide is on.
+ //this.panel.peekPanel(); //no longer works on cinnamon 5.4.x
+
+ //center menu if applet in center zone of top or bottom panel
+ const appletDefinition = AppletManager.getAppletDefinition({applet_id: this.instance_id});
+ if ((this.orientation === St.Side.BOTTOM || this.orientation === St.Side.TOP) &&
+ appletDefinition.location_label === 'center') {
+ const monitor = Main.layoutManager.findMonitorForActor(this.menu.actor);
+ this.menu.shiftToPosition(Math.floor(monitor.width / 2) + monitor.x);
+ }
+
+ //By default, current active category button will have focus. If categories are
+ //hidden, give focus to first app item.
+ if (!this.settings.showCategories) {
+ this.display.appsView.focusFirstItem();
+ }
+
+ return true;
+ }
+
+ _onMenuClosed() {
+ if (this.searchActive) {
+ this._endSearchMode();
+ }
+ this.display.clearFocusedActors();
+ this.display.appsView.clearApps();//for quicker reopening of menu
+ }
+
+ _onMenuKeyPress(actor, event) {
+ if (this.resizer.resizingInProgress) {
+ return Clutter.EVENT_STOP;
+ }
+
+ const symbol = event.get_key_symbol();
+ const keyCode = event.get_key_code();
+ const modifierState = Cinnamon.get_event_state(event);
+
+ /* check for a keybinding and quit early, otherwise we get a double hit
+ of the keybinding callback */
+ const action = global.display.get_keybinding_action(keyCode, modifierState);
+ if (action === Meta.KeyBindingAction.CUSTOM) {
+ return Clutter.EVENT_PROPAGATE;
+ }
+
+ const ctrlKey = modifierState === 4;
+ const shiftKey = modifierState === 1;
+ const altKey = modifierState === 8;
+ //const altgrKey = modifierState === 128;
+ const noModifiers = modifierState === 0;
+
+ //Because Clutter.EVENT_PROPAGATE is returned on KEY_Left and KEY_Right, ignore duplicate
+ //event emitted by ibus. https://github.com/linuxmint/cinnamon-spices-applets/issues/3294
+ if (!this.lastKeyEventTime) this.lastKeyEventTime = 0;
+ const now = Date.now();
+ if ((symbol === Clutter.KEY_Left || symbol === Clutter.KEY_KP_Left
+ || symbol === Clutter.KEY_Right || symbol === Clutter.KEY_KP_Right)
+ && noModifiers && (now - this.lastKeyEventTime) <= 80) {
+ return Clutter.EVENT_PROPAGATE;
+ }
+ this.lastKeyEventTime = now;
+
+ const contextMenuButtons = this.display.contextMenu.contextMenuButtons;
+ const appButtons = this.display.appsView.getActiveButtons();
+ const sidebarButtons = this.display.sidebar.getButtons();
+ const categoryButtons = this.display.categoriesView.buttons;
+
+ const focusedContextMenuItemIndex = this.display.contextMenu.getCurrentlyFocusedMenuItem();
+ let focusedAppItemIndex = appButtons.findIndex(button => button.has_focus);
+ const focusedSidebarItemIndex = sidebarButtons.findIndex(button => button.has_focus);
+ //When "activate categories on click" option is set, currentlyActiveCategoryIndex and
+ //focusedCategoryIndex may not be the same.
+ const focusedCategoryIndex = categoryButtons.findIndex(button => button.has_focus);
+
+ let currentlyActiveCategoryIndex = categoryButtons.findIndex(button =>
+ this.currentCategory === button.id);
+ if (currentlyActiveCategoryIndex < 0) {
+ currentlyActiveCategoryIndex = 0;
+ }
+
+ const focusedContextMenuItemExists = focusedContextMenuItemIndex > -1;
+ let focusedAppItemExists = focusedAppItemIndex > -1;
+ const focusedSidebarItemExists = focusedSidebarItemIndex > -1;
+ const focusedCategoryExists = focusedCategoryIndex > -1;
+
+ if (!focusedContextMenuItemExists && !focusedAppItemExists &&
+ !focusedSidebarItemExists && !focusedCategoryExists) {
+ //todo: No focused item, ideally this shouldn't happen
+ if (appButtons[0]) {
+ appButtons[0].handleEnter();
+ focusedAppItemIndex = 0;
+ focusedAppItemExists = true;
+ }
+ }
+
+ const leaveCurrentlyFocusedItem = () => {
+ if (focusedContextMenuItemExists) {
+ contextMenuButtons[focusedContextMenuItemIndex].handleLeave();
+ } else if (focusedAppItemExists) {
+ appButtons[focusedAppItemIndex].handleLeave();
+ } else if (focusedSidebarItemExists) {
+ sidebarButtons[focusedSidebarItemIndex].handleLeave();
+ } else if (focusedCategoryExists) {
+ categoryButtons[focusedCategoryIndex].removeFocusAndHover();
+ }
+ };
+
+ let tabRight = () => {
+ if (focusedContextMenuItemExists) {
+ //effectively ignore keypress
+ contextMenuButtons[focusedContextMenuItemIndex].handleEnter();
+ } else if (!this.searchActive && this.settings.showCategories &&
+ (focusedSidebarItemExists ||
+ focusedAppItemExists && !this.settings.showSidebar)) {
+ categoryButtons[currentlyActiveCategoryIndex].handleEnter();
+ } else if (focusedAppItemExists && this.settings.showSidebar) {
+ sidebarButtons[0].handleEnter();
+ } else {
+ appButtons[0].handleEnter();
+ }
+ }
+
+ let tabLeft = () => {
+ if (focusedContextMenuItemExists) {
+ //effectively ignore keypress
+ contextMenuButtons[focusedContextMenuItemIndex].handleEnter();
+ } else if (focusedAppItemExists && !this.searchActive &&
+ this.settings.showCategories) {
+ categoryButtons[currentlyActiveCategoryIndex].handleEnter();
+ } else if (this.settings.showSidebar && (focusedCategoryExists ||
+ focusedAppItemExists && (this.searchActive || !this.settings.showCategories))) {
+ sidebarButtons[0].handleEnter();
+ } else {
+ appButtons[0].handleEnter();
+ }
+ }
+
+ if (St.Widget.get_default_direction() === St.TextDirection.RTL) {
+ [tabRight, tabLeft] = [tabLeft, tabRight];
+ }
+
+ const getNextSidebarItemIndex = () => {
+ if (focusedSidebarItemIndex < sidebarButtons.length - 1) {
+ return focusedSidebarItemIndex + 1;
+ } else {
+ return 0;
+ }
+ };
+
+ const getPreviousSidebarItemIndex = () => {
+ if (focusedSidebarItemIndex === 0) {
+ return sidebarButtons.length -1;
+ } else {
+ return focusedSidebarItemIndex - 1;
+ }
+ };
+
+ const leftNavigation = () => {
+ if (focusedContextMenuItemExists) {
+ contextMenuButtons[focusedContextMenuItemIndex].handleEnter();//effectively ignore
+ } else if (focusedAppItemExists) {
+ if (focusedAppItemIndex > 0) {
+ appButtons[focusedAppItemIndex - 1].handleEnter();
+ } else {
+ appButtons[appButtons.length - 1].handleEnter();
+ }
+ } else if (focusedSidebarItemExists) {
+ if (this.settings.sidebarPlacement === SidebarPlacement.LEFT ||
+ this.settings.sidebarPlacement === SidebarPlacement.RIGHT) {
+ tabLeft();
+ } else {
+ sidebarButtons[getPreviousSidebarItemIndex()].handleEnter();
+ }
+ } else if (focusedCategoryExists) {
+ tabLeft();
+ }
+ };
+
+ const rightNavigation = () => {
+ if (focusedContextMenuItemExists) {
+ contextMenuButtons[focusedContextMenuItemIndex].handleEnter();//effectively ignore keypress
+ } else if (focusedAppItemExists) {
+ if (appButtons[focusedAppItemIndex + 1]) {
+ appButtons[focusedAppItemIndex + 1].handleEnter();
+ } else {
+ appButtons[0].handleEnter();
+ }
+ } else if (focusedSidebarItemExists) {
+ if (this.settings.sidebarPlacement === SidebarPlacement.LEFT ||
+ this.settings.sidebarPlacement === SidebarPlacement.RIGHT) {
+ tabRight();;
+ } else {
+ sidebarButtons[getNextSidebarItemIndex()].handleEnter();
+ }
+ } else if (focusedCategoryExists) {
+ tabRight();
+ }
+ };
+
+ const downNavigation = () => {
+ if (focusedContextMenuItemExists) {
+ let nextContextMenuItem = focusedContextMenuItemIndex + 1;
+ while (!contextMenuButtons[nextContextMenuItem] ||
+ contextMenuButtons[nextContextMenuItem].action === null) {
+ nextContextMenuItem++;
+ if (nextContextMenuItem >= contextMenuButtons.length) {
+ nextContextMenuItem = 0;
+ }
+ }
+ contextMenuButtons[nextContextMenuItem].handleEnter();
+ } else if (focusedAppItemExists) {
+ if (appButtons[focusedAppItemIndex + 1]) {
+ const column = appButtons[focusedAppItemIndex].actor.layout_column;
+ let next = focusedAppItemIndex + 1;
+ while (appButtons[next].actor.layout_column != column && appButtons[next + 1]) {
+ next++;
+ }
+ appButtons[next].handleEnter();
+ } else {
+ appButtons[focusedAppItemIndex].handleEnter();//effectively no change
+ }
+ } else if (focusedSidebarItemExists) {
+ if (this.settings.sidebarPlacement === SidebarPlacement.TOP) {
+ tabRight();
+ } else if (this.settings.sidebarPlacement === SidebarPlacement.BOTTOM) {
+ tabLeft();
+ } else {
+ sidebarButtons[getNextSidebarItemIndex()].handleEnter();
+ }
+ } else if (focusedCategoryExists) {
+ if (categoryButtons[focusedCategoryIndex + 1]) {
+ categoryButtons[focusedCategoryIndex + 1].handleEnter();
+ } else {
+ categoryButtons[0].handleEnter();
+ }
+ }
+ };
+
+ const upNavigation = () => {
+ if (focusedContextMenuItemExists) {
+ let previousContextMenuItem = focusedContextMenuItemIndex - 1;
+ while (!contextMenuButtons[previousContextMenuItem] ||
+ contextMenuButtons[previousContextMenuItem].action === null) {
+ previousContextMenuItem--;
+ if (previousContextMenuItem < 0) {
+ previousContextMenuItem = contextMenuButtons.length -1;
+ }
+ }
+ contextMenuButtons[previousContextMenuItem].handleEnter();
+ } else if (focusedAppItemExists) {
+ if (focusedAppItemIndex > 0) {
+ const column = appButtons[focusedAppItemIndex].actor.layout_column;
+ let previous = focusedAppItemIndex - 1;
+ while (appButtons[previous].actor.layout_column != column && previous > 0) {
+ previous--;
+ }
+ appButtons[previous].handleEnter();
+ } else {
+ appButtons[0].handleEnter();//effectively no change
+ }
+ } else if (focusedSidebarItemExists) {
+ if (this.settings.sidebarPlacement === SidebarPlacement.TOP) {
+ tabLeft();
+ } else if (this.settings.sidebarPlacement === SidebarPlacement.BOTTOM) {
+ tabRight();
+ } else {
+ sidebarButtons[getPreviousSidebarItemIndex()].handleEnter();
+ }
+ } else if (focusedCategoryExists) {
+ if (focusedCategoryIndex > 0) {
+ categoryButtons[focusedCategoryIndex - 1].handleEnter();
+ } else {
+ categoryButtons[categoryButtons.length - 1].handleEnter();
+ }
+ }
+ };
+
+ switch (true) {
+ case (symbol === Clutter.KEY_KP_Enter || symbol === Clutter.KP_Enter ||
+ symbol === Clutter.KEY_Return) && ctrlKey:
+ case symbol === Clutter.KEY_Menu && noModifiers:
+ if (this.display.contextMenu.isOpen) {
+ this.display.contextMenu.close();
+ } else if (focusedAppItemExists) {
+ appButtons[focusedAppItemIndex].openContextMenu();
+ } else if (focusedSidebarItemExists) {
+ sidebarButtons[focusedSidebarItemIndex].openContextMenu();
+ } else if (focusedCategoryExists) {
+ categoryButtons[focusedCategoryIndex].openContextMenu();
+ }
+ return Clutter.EVENT_STOP;
+ case (symbol === Clutter.KP_Enter || symbol === Clutter.KEY_KP_Enter ||
+ symbol === Clutter.KEY_Return) && noModifiers:
+ if (focusedContextMenuItemExists) {
+ contextMenuButtons[focusedContextMenuItemIndex].activate();
+ } else if (focusedAppItemExists) {
+ appButtons[focusedAppItemIndex].activate();
+ } else if (focusedSidebarItemExists) {
+ sidebarButtons[focusedSidebarItemIndex].activate();
+ } else if (focusedCategoryExists) {
+ categoryButtons[focusedCategoryIndex].selectCategory();
+ }
+ return Clutter.EVENT_STOP;
+ case symbol === Clutter.unicode_to_keysym("p".charCodeAt(0)) && ctrlKey:
+ if (focusedAppItemExists && appButtons[focusedAppItemIndex].app.isApplication) {
+ const desktop_file_path = appButtons[focusedAppItemIndex].app.desktop_file_path;
+ Util.spawn(['cinnamon-desktop-editor', '-mlauncher', '-o' + desktop_file_path]);
+ this.menu.close();
+ return Clutter.EVENT_STOP;
+ }
+ return Clutter.EVENT_PROPAGATE
+ case (symbol === Clutter.KEY_Up || symbol === Clutter.KEY_KP_Up) && noModifiers:
+ leaveCurrentlyFocusedItem();
+ upNavigation();
+ return Clutter.EVENT_STOP;
+ case (symbol === Clutter.KEY_Down || symbol === Clutter.KEY_KP_Down) && noModifiers:
+ leaveCurrentlyFocusedItem();
+ downNavigation();
+ return Clutter.EVENT_STOP;
+ case (symbol === Clutter.KEY_Right || symbol === Clutter.KEY_KP_Right) && noModifiers:
+ leaveCurrentlyFocusedItem();
+ rightNavigation();
+ return Clutter.EVENT_PROPAGATE; // so that left/right can also be used to
+ // navigate search entry
+ case (symbol === Clutter.KEY_Left || symbol === Clutter.KEY_KP_Left) && noModifiers:
+ leaveCurrentlyFocusedItem();
+ leftNavigation();
+ return Clutter.EVENT_PROPAGATE; // so that left/right can also be used to
+ // navigate search entry
+ case (symbol === Clutter.Tab || symbol === Clutter.KEY_Tab) && noModifiers:
+ leaveCurrentlyFocusedItem();
+ if (St.Widget.get_default_direction() === St.TextDirection.RTL) {
+ //If direction is RTL then tabLeft() and tabRight() have already been swapped to
+ //account for layout change and so that arrow keys work correctly. But tab key is
+ //expected move to the left in RTL languages so swap again here.
+ tabLeft();
+ } else {
+ tabRight();
+ }
+ return Clutter.EVENT_STOP;
+ case (symbol === Clutter.KEY_ISO_Left_Tab || symbol === Clutter.ISO_Left_Tab ||
+ (symbol === Clutter.Tab || symbol === Clutter.KEY_Tab) && shiftKey):
+ leaveCurrentlyFocusedItem();
+ if (St.Widget.get_default_direction() === St.TextDirection.RTL) {
+ tabRight();
+ } else {
+ tabLeft()
+ }
+ return Clutter.EVENT_STOP;
+ case symbol === Clutter.Tab && altKey:
+ this.menu.close();//Close menu as alt-tab is used for app-switcher in cinnamon
+ return Clutter.EVENT_STOP;
+ case (symbol === Clutter.Escape || symbol === Clutter.KEY_Escape) && noModifiers:
+ if (this.display.contextMenu.isOpen) {
+ this.display.contextMenu.close();
+ } else {
+ this.menu.close();
+ }
+ return Clutter.EVENT_STOP;
+ case symbol === Clutter.KEY_Page_Up && noModifiers:
+ leaveCurrentlyFocusedItem();
+ if (focusedAppItemExists) {
+ appButtons[0].handleEnter();
+ } else if (focusedSidebarItemExists) {
+ sidebarButtons[0].handleEnter();
+ } else {
+ categoryButtons[0].handleEnter();
+ }
+ return Clutter.EVENT_STOP;
+ case symbol === Clutter.KEY_Page_Down && noModifiers:
+ leaveCurrentlyFocusedItem();
+ if (focusedAppItemExists) {
+ appButtons[appButtons.length - 1].handleEnter();
+ } else if (focusedSidebarItemExists) {
+ sidebarButtons[sidebarButtons.length - 1].handleEnter();
+ } else {
+ categoryButtons[categoryButtons.length - 1].handleEnter();
+ }
+ return Clutter.EVENT_STOP;
+ default:
+ return Clutter.EVENT_PROPAGATE;
+ }
+ }
+
+ getNumberOfItemsToFitColumns(minimumItems) {
+ //adjust number of items according to number of columns to make
+ //best use of available space.
+ const columns = this.display.appsView.getGridValues().columns;
+ return Math.ceil(minimumItems / columns) * columns;
+ }
+
+ setActiveCategory(categoryId) {
+ // categoryId is one of 3 things: a special category (one of 'places', 'recents',
+ // 'favorite_files' or 'favorite_apps'), an application category id,
+ // or an absolute path used in folderview (must begin with a /)
+ this.currentCategory = categoryId;
+ this.display.categoriesView.setSelectedCategoryStyle(categoryId);
+ this.display.categoriesView.setCategoryFocus(categoryId);
+ this.display.appsView.buttonStoreCleanup();
+
+ switch (categoryId) {
+ case 'places':
+ this.display.appsView.populate(this.listPlaces());
+ break;
+ case 'recents':
+ const maxItems = this.getNumberOfItemsToFitColumns(6);
+ const maxRecentApps = this.getNumberOfItemsToFitColumns(4);
+
+ this.display.appsView.populate_init();
+ const recentApps = this.listRecent_apps(maxRecentApps);
+ if (recentApps.length > 0) {
+ this.display.appsView.populate_add(recentApps,_('Applications'));
+ }
+ const recentDocs = this.listRecentByType('documents', maxItems);
+ if (recentDocs.length > 0) {
+ this.display.appsView.populate_add(recentDocs,_('Documents'));
+ }
+ const recentVids = this.listRecentByType('video', maxItems);
+ if (recentVids.length > 0) {
+ this.display.appsView.populate_add(recentVids,_('Videos'));
+ }
+ const recentPics = this.listRecentByType('image', maxItems);
+ if (recentPics.length > 0) {
+ this.display.appsView.populate_add(recentPics,_('Images'));
+ }
+ const recentAudio = this.listRecentByType('audio', maxItems);
+ if (recentAudio.length > 0) {
+ this.display.appsView.populate_add(recentAudio,_('Music'));
+ }
+ const totalItems = recentApps.length + recentDocs.length + recentVids.length +
+ recentPics.length + recentAudio.length;
+ if (totalItems > 0) {
+ this.display.appsView.populate_add(this.getClearRecentsButton());
+ }
+ this.display.appsView.populate_finish();
+ if (totalItems == 0) {
+ this.display.appsView.populate([], _('No recent Items'));
+ }
+ break;
+ case 'favorite_files':
+ this.display.appsView.populate(this.listFavoriteFiles());
+ break;
+ case 'favorite_apps':
+ this.display.appsView.populate(this.listFavoriteApps());
+ break;
+ default:
+ if (categoryId.startsWith('/')) {//folder view
+ const folderContents = this.listFolder(categoryId);
+ const headerText = folderContents.errorMsg? folderContents.errorMsg : categoryId;
+ this.display.appsView.populate(folderContents.results, headerText);
+ } else if (categoryId === 'all') {
+ this.display.appsView.populate_init();
+ this.display.appsView.populate_add(this.apps.listApplications('allApps'));
+ this.display.appsView.populate_add(
+ this.apps.listApplications('allSettings'), _("Settings"));
+ this.display.appsView.populate_finish();
+ } else {//other applications categories
+ this.display.appsView.populate(this.apps.listApplications(categoryId));
+ }
+ }
+ }
+//==============search==============
+ _onSearchTextChanged() {
+ const searchText = this.display.searchView.searchEntryText.get_text();
+
+ if (searchText.length === 0) {//search text deleted, cancel search mode
+ if (!this.searchActive) {//search mode already ended
+ return;
+ }
+ this._endSearchMode();
+ this.setActiveCategory(this.currentCategory);
+
+ //By default, current active category button will have focus. If categories are
+ //hidden, give focus to first app item.
+ if (!this.settings.showCategories) {
+ this.display.appsView.focusFirstItem();
+ }
+ return;
+ }
+
+ //---start search---
+
+ //Set a new search ID so that async search functions
+ //from a previous search can be aborted.
+ this.currentSearchId = Math.floor(Math.random() * 100000000);
+
+ this.display.clearFocusedActors();
+ if (!this.searchActive) {//set search mode
+ this.searchActive = true;
+ this.display.searchView.showAndConnectSecondaryIcon();//show edit-delete icon
+ this.display.categoriesView.buttons.forEach(button => button.disable());
+ }
+
+ // When doSearch() below is called by Meta.later_add, this.currentSearchId may have changed
+ // so store its current value in a const as the current lexical scope is preserved.
+ const currentSearchId = this.currentSearchId;
+ Meta.later_add(Meta.LaterType.IDLE, () => this._doSearch(searchText, currentSearchId));
+ }
+
+ _endSearchMode() {
+ this.searchActive = false;
+ this.display.searchView.hideAndDisconnectSecondaryIcon();//hide edit-delete icon
+ this.display.categoriesView.buttons.forEach(button => button.enable());
+ this.display.searchView.searchEntry.set_text('');
+ this.previousSearchPattern = '';
+ }
+
+ _doSearch(pattern_raw, thisSearchId) {
+ //this function has been called asynchronously meaning that a keypress may have changed the
+ //search query before this function is called. Check that this search is still valid.
+ if (!this.searchActive || thisSearchId !== this.currentSearchId) {
+ return;
+ }
+
+ const pattern = graphemeBaseChars(pattern_raw).toLocaleUpperCase().trim();
+
+ //Don't repeat the same search. This can happen if a key and backspace are pressed in quick
+ //succession while a previous search is being carried out.
+ if (pattern_raw === this.previousSearchPattern) {
+ return;
+ }
+ this.previousSearchPattern = pattern_raw;
+
+ let EMOJI_PREFIX = false;
+ let FILE_PREFIX = false;
+ if (pattern.length > 2) {
+ if (pattern.startsWith('E ')) {
+ EMOJI_PREFIX = true;
+ }
+ if (pattern.startsWith('F ')) {
+ FILE_PREFIX = true;
+ }
+ }
+ const PREFIX_USED = EMOJI_PREFIX || FILE_PREFIX;
+
+ //======Begin search===========
+ let applicationResults = [];
+ let fileResults = [];
+ let otherResults = [];
+ if (!PREFIX_USED) {
+ applicationResults = this.apps.searchApplications(pattern);
+ fileResults = this.searchFavoriteFiles(pattern)
+ .concat(this.recentsEnabled ? this.searchRecent(pattern) : []);
+ otherResults = this.settings.showPlaces ? this.searchPlaces(pattern) : [];
+ }
+ const emojiResults = [];
+
+ //-----
+
+ const showResults = () => {//sort and display all search results
+ if (!this.searchActive || thisSearchId != this.currentSearchId){
+ return; //Search mode has ended or search string has changed
+ }
+
+ // sort applicationResults[]
+ applicationResults.sort((a, b) => b.score - a.score);
+
+ // sort fileResults[]
+ fileResults.sort((a, b) => b.score - a.score); // items with equal score are left in
+ // existing order
+
+ if (fileResults.length > 25) { // remove poor results to save time.
+ fileResults.length = 25;
+ }
+ // Remove duplicate fileResults[]. eg. a fav file, a recent file and a folderfile might all
+ // be the same file. Prefer from highest to lowest: isFavoriteFile, isRecentFile,
+ // isFolderviewFile which is easy because fileResults[] should already be in this order.
+ for (let i = 0; i < fileResults.length -1; i++) {
+ const app = fileResults[i];
+ if (app.isFavoriteFile || app.isRecentFile) {
+ for (let r = i + 1; r < fileResults.length; r++) {
+ const compareApp = fileResults[r];
+ if ((compareApp.isRecentFile || compareApp.isFolderviewFile)
+ && compareApp.uri === app.uri) {
+ fileResults.splice(r, 1);
+ r--;
+ }
+ }
+ }
+ }
+
+ // Limit applicationResults to 6
+ applicationResults.length = Math.min(applicationResults.length, this.getNumberOfItemsToFitColumns(6));
+ // Limit fileResults to 10
+ fileResults.length = Math.min(fileResults.length, this.getNumberOfItemsToFitColumns(10));
+
+ // Display results
+ this.display.appsView.populate_init(calculatorResult);
+ if (applicationResults.length > 0) {
+ this.display.appsView.populate_add(applicationResults, _('Applications'));
+ }
+ if (fileResults.length > 0) {
+ this.display.appsView.populate_add(fileResults, _('Files'));
+ }
+ if (otherResults.length > 0) {
+ this.display.appsView.populate_add(otherResults, _('Other search results'));
+ }
+ if (emojiResults.length > 0) {
+ this.display.appsView.populate_add(emojiResults, _('Emoji'));
+ }
+ this.display.appsView.populate_finish();
+
+ //In case mouse is hovering a different item (thus selecting it) ensure first result
+ //is highlighted after drawing so that pressing return selects top result.
+ Meta.later_add(Meta.LaterType.IDLE, () => this.display.appsView.focusFirstItem());
+ };
+
+ //=======search providers==========
+ //---calculator---
+ let calculatorResult = null;
+ let ans = null;
+ const exp = pattern_raw.replace(/([a-zA-Z][a-zA-Z0-9_]*)/g, (match) => `Math.${match}`);
+
+ try {
+ ans = eval?.(`"use strict"; ${exp}`);
+ } catch(e) {
+ const probablyMath = /[\(\)\+=/\*]/.test(exp);
+ if (probablyMath) {
+ calculatorResult = _("Calculator: ") + e.message;
+ }
+ }
+
+ if ((typeof ans === 'number' || typeof ans === 'boolean' || typeof ans === 'bigint')
+ && ans != pattern_raw ) {
+
+ let ans_str = ans.toString();
+ //remove rounding error
+ if (typeof ans === 'number') {
+ if (ans > Number.MAX_SAFE_INTEGER || ans < Number.MIN_SAFE_INTEGER) {
+ // JS will show up to 21 digits of an integer (inaccurately) even though
+ // only 16 are significant, so show in exponential form instead.
+ ans_str = Number(ans.toPrecision(16)).toExponential();
+ } else {
+ ans_str = Number(ans.toPrecision(16)).toString();
+ }
+ }
+
+ otherResults.push({
+ isSearchResult: true,
+ name: ans_str,
+ description: _('Click to copy'),
+ deleteAfterUse: true,
+ icon: new St.Icon({
+ icon_name: 'accessories-calculator',
+ icon_type: St.IconType.FULLCOLOR,
+ icon_size: this.settings.appsGridIconSize
+ }),
+ activate: () => {
+ const clipboard = St.Clipboard.get_default();
+ clipboard.set_text(St.ClipboardType.CLIPBOARD, ans_str);
+ }
+ });
+ calculatorResult = pattern_raw + " = " + ans_str;
+ }
+
+ //---emoji search------
+ if (pattern.length > 2 && this.settings.enableEmojiSearch && !PREFIX_USED ||
+ EMOJI_PREFIX && pattern.length >= 4) {
+ let epattern = pattern;
+ if (EMOJI_PREFIX) {
+ epattern = pattern.substring(2);
+ }
+
+ EMOJI.forEach(emoji => {
+ const nameScore = searchStr(epattern, emoji[EMOJI_NAME], true);
+ let keywordScore = searchStr(epattern, emoji[EMOJI_KEYWORDS], true);
+ keywordScore *= 0.95; //slightly lower priority for keyword match
+ const bestScore = Math.max(nameScore, keywordScore);
+ if (bestScore > SEARCH_THRESHOLD) {
+ emojiResults.push({
+ name: emoji[EMOJI_NAME],
+ score: bestScore,
+ description: _('Click to copy'),
+ isSearchResult: true,
+ deleteAfterUse: true,
+ emoji: emoji[EMOJI_CODE],
+ activate: () => {
+ const clipboard = St.Clipboard.get_default();
+ clipboard.set_text(St.ClipboardType.CLIPBOARD, emoji[EMOJI_CODE]);
+ }
+ });
+ }
+ });
+
+ emojiResults.sort((a, b) => a.score < b.score);
+ if (emojiResults.length > 36) {
+ emojiResults.length = 36;
+ }
+ }
+
+ //----home folder search--------
+ Meta.later_add(Meta.LaterType.IDLE, () => {
+ if (!(pattern.length > 1 && this.settings.searchHomeFolder && !PREFIX_USED ||
+ FILE_PREFIX && pattern.length >= 3)) {
+ return;
+ }
+ if (!this.searchActive || thisSearchId !== this.currentSearchId) {
+ return;
+ }
+
+ let fpattern = pattern;
+ if (FILE_PREFIX) {
+ fpattern = pattern.substring(2);
+ }
+ // Call function searchNextDir() consecutively and asynchronously on each folder to be searched so
+ // that search can be interupted at any time. Starting with home folder, all folders to be
+ // searched are added to foldersToDo[]. Searching is cancelled when the search string has
+ // changed (thisSearchId !== this.currentSearchId).
+
+ let updateInterval = 100;//update the results after the first 100ms even if search hasn't finished
+ const MAX_FOLDERS_TO_SEARCH = 50000;
+ const FOLLOW_SYMLINKS = false;
+ const FILE_SEARCH_DEBUG = false;
+ const results = [];
+ const foldersToDo = [];
+ foldersToDo.push(GLib.get_home_dir());// start search in home directory
+ let foldersSearched = 0;
+ let lastUpdateTime = Date.now();
+ const total_timer=Date.now();
+
+ const searchNextDir = (thisSearchId) => {
+ const folder = foldersToDo.pop();
+
+ const dir = Gio.file_new_for_path(folder);
+ let enumerator;
+ let timer=Date.now();
+ if (FILE_SEARCH_DEBUG) {
+ log("searching: " + folder);
+ }
+
+ dir.enumerate_children_async('standard::name,standard::type,standard::is-symlink',
+ Gio.FileQueryInfoFlags.NONE, GLib.PRIORITY_DEFAULT, null, (source, result) => {
+ try {
+ enumerator = source.enumerate_children_finish(result);
+ } catch(e) {
+ global.logWarning('gridmenu file search:' + e.message);
+ }
+ if (!this.searchActive || thisSearchId !== this.currentSearchId) {
+ if (enumerator) {
+ enumerator.close(null);
+ }
+ return;
+ }
+
+ //find matching files and folders in directory
+ if (enumerator) {
+ enumerator.next_files_async(1000, GLib.PRIORITY_DEFAULT, null, (source, result) => {
+ let fileInfos;
+ try {
+ fileInfos = source.next_files_finish(result);
+ } catch(e) {
+ global.logWarning('Cinnamenu file search:' + e.message);
+ }
+ if (!this.searchActive || thisSearchId !== this.currentSearchId) return;
+ if (fileInfos) {
+ fileInfos.forEach((fileInfo) => {
+ const filename = fileInfo.get_name();
+ if (filename.startsWith(".")) {
+ return; // skip hidden files
+ }
+ const isDirectory = fileInfo.get_file_type() === Gio.FileType.DIRECTORY;
+ const filePath = folder + (folder === '/' ? '' : '/') + filename;
+ let matchScore = searchStr(fpattern, filename, true, true);
+ if (matchScore > 1) { //any word boundary match
+ const file = Gio.file_new_for_path(filePath);
+ const extraFileInfo = file.query_info('standard::icon,standard::content-type',
+ Gio.FileQueryInfoFlags.NONE, null);
+ matchScore -= 0.01;
+ //if file then treat as isFolderviewFile and if directory then treat as isPlace
+ const foundFile = {
+ name: filename,
+ score: matchScore * (fpattern.length > 2 ? 1 : 0.9),
+ gicon: extraFileInfo.get_icon(),
+ uri: file.get_uri(),
+ mimeType: extraFileInfo.get_content_type(),
+ description: filePath,
+ isPlace: isDirectory,
+ isDirectory: isDirectory,
+ isFolderviewFile: !isDirectory,
+ deleteAfterUse: true
+ };
+ if (isDirectory) {
+ const defaultInfo =
+ Gio.AppInfo.get_default_for_type('inode/directory', false);
+ if (defaultInfo) {
+ foundFile.activate = () => { defaultInfo.launch([file], null); };
+ }
+ }
+ results.push(foundFile);
+ }
+
+ //Add subdirectories to foldersToDo[]
+ if (isDirectory && (!fileInfo.get_is_symlink() || FOLLOW_SYMLINKS) &&
+ foldersSearched < MAX_FOLDERS_TO_SEARCH) {
+ foldersToDo.push(filePath);
+ }
+ });//end forEach
+ }//end if
+
+ if (FILE_SEARCH_DEBUG) {
+ log("todo: " + foldersToDo.length + " done: " + foldersSearched + " time: " + (Date.now() - timer) + " : total " + (Date.now() - total_timer));
+ }
+ //update display of results at intervals or when search completed
+ if (foldersToDo.length === 0 || Date.now() - lastUpdateTime > updateInterval) {
+ if (results.length > 0 && this.searchActive &&
+ thisSearchId === this.currentSearchId) {
+ fileResults = fileResults.concat(results);
+ showResults();
+ results.length = 0;
+ }
+ lastUpdateTime = Date.now();
+ updateInterval *= 2;//progressively longer update intervals
+ }
+
+ //continue search if not completed
+ if (foldersToDo.length > 0) {
+ foldersSearched++;
+ Meta.later_add(Meta.LaterType.IDLE, () => { searchNextDir(thisSearchId); });
+ }
+ if (enumerator) {
+ enumerator.close(null);
+ }
+ }); //end of next_files_async
+ }//end if
+ });//end of enumerate_children_async
+ };// end searchNextDir()
+
+ searchNextDir(this.currentSearchId);
+ });
+
+ ///----search providers--------
+ Meta.later_add(Meta.LaterType.IDLE, () => {
+ if (!this.searchActive || thisSearchId !== this.currentSearchId) {
+ return;
+ }
+ launch_all(pattern, (provider, providerResults) => {
+ providerResults.forEach(providerResult => {
+ if (!providerResult) {
+ return;
+ }
+ providerResult.isSearchResult = true;
+ providerResult.name = providerResult.label.replace(/ : /g, ': ');
+ providerResult.activate = provider.on_result_selected;
+ providerResult.deleteAfterUse = true;
+ //providerResult.score = 0.2;
+ if (providerResult.icon) {
+ providerResult.icon.icon_size = this.settings.appsGridIconSize;
+ } else if (providerResult.icon_app) {
+ providerResult.icon = providerResult.icon_app.create_icon_texture(
+ this.settings.appsGridIconSize);
+ } else if (providerResult.icon_filename) {
+ providerResult.icon = new St.Icon({
+ gicon: new Gio.FileIcon({
+ file: Gio.file_new_for_path(providerResult.icon_filename)}),
+ icon_size: this.settings.appsGridIconSize
+ });
+ }
+ });
+ if (!this.searchActive || thisSearchId !== this.currentSearchId ||
+ !providerResults || providerResults.length === 0) {
+ return;
+ }
+ otherResults = otherResults.concat(providerResults);
+ showResults();
+ });
+ });
+
+ showResults();
+ return;
+ }
+
+/* Below are all functions creating arrays of app objects excluding _doSearch() and
+ * listApplications() which is in Apps class. Arrays of app objs are then passed
+ * to AppsView.populate() which creates AppButtons with .app as a property.
+ *
+ * app obj properties used:
+ * .name
+ * .description
+ * .id
+ * .uri
+ * .mimeType
+ * .icon
+ * .gicon
+ * .iconFactory()
+ * .score
+ * .desktop_file_path
+ * .isApplication
+ * .isPlace
+ * .isRecentFile
+ * .isClearRecentsButton
+ * .isFavoriteFile //Nemo favorites
+ * .isFolderviewFile
+ * .isDirectory
+ * .isBackButton
+ * .isSearchResult
+ * .deleteAfterUse
+ * .emoji
+ * .activate()
+ */
+
+ listFavoriteApps() {
+ const res = this.appFavorites.getFavorites();
+ res.forEach(favApp => {
+ favApp.name = favApp.get_name();
+ favApp.description = favApp.get_description();
+ favApp.isApplication = true;
+ });
+ return res;
+ }
+
+ searchRecent(pattern) {
+ const res = [];
+
+ this.listRecentByType('all', 100).forEach(recentItem => {
+ const score = searchStr(pattern, recentItem.name);
+ if (recentItem.name && score > SEARCH_THRESHOLD) {
+ recentItem.score = score;
+ res.push(recentItem);
+ }
+ });
+
+ return res;
+ }
+
+ listRecent_apps(maxRecentItems) {
+ const res = [];
+
+ this.recentApps.getApps(maxRecentItems).forEach(recentId => {
+ const app = this.apps.listApplications('all').find(app => app.id === recentId);
+ if (app) {//Check because app may have been uninstalled
+ res.push(app);
+ }
+ });
+
+ return res;
+ }
+
+ listRecentByType(type, maxItems) {
+ //param "type" is one of all|documents|video|image|audio.
+ const res = [];
+ this.recentManagerDefault.get_items().forEach(recentInfo => {
+ if (type === 'documents' && ( recentInfo.get_mime_type().startsWith('video') ||
+ recentInfo.get_mime_type().startsWith('image') ||
+ recentInfo.get_mime_type().startsWith('audio') )) {
+ return;
+ }
+ if ((type === 'video' || type === 'image' || type === 'audio') &&
+ !recentInfo.get_mime_type().startsWith(type)) {
+ return;
+ }
+
+ const new_recent = {
+ name: recentInfo.get_display_name(),
+ gicon: recentInfo.get_gicon(),
+ uri: recentInfo.get_uri(),
+ mimeType: recentInfo.get_mime_type(),
+ description: recentInfo.get_uri_display(),
+ modifiedTime: recentInfo.get_modified(),//only used for sorting below
+ isRecentFile: true,
+ deleteAfterUse: true
+ };
+ res.push(new_recent);
+ });
+ res.sort((a, b) => a.modifiedTime < b.modifiedTime);
+ if (res.length > maxItems) {
+ res.length = maxItems;
+ }
+ return res;
+ }
+
+ getClearRecentsButton() {
+ const res = [];
+
+ const clearRecentsButton =
+ this.display.appsView.buttonStore.find(button => button.app.isClearRecentsButton);
+ if (clearRecentsButton) {
+ res.push(clearRecentsButton.app);
+ } else {
+ res.push({
+ name: _('Clear List'),
+ description: '',
+ icon: new St.Icon({
+ icon_name: 'edit-clear',
+ icon_type: St.IconType.SYMBOLIC,
+ icon_size: this.settings.appsGridIconSize
+ }),
+ isClearRecentsButton: true
+ });
+ }
+
+ return res;
+ }
+
+ listPlaces() {
+ const res = [];
+ Main.placesManager.getAllPlaces().forEach(place => {
+ let selectedAppId = place.idDecoded.substr(place.idDecoded.indexOf(':') + 1);
+ const fileIndex = selectedAppId.indexOf('file:///');
+ if (fileIndex !== -1) {
+ selectedAppId = selectedAppId.substr(fileIndex + 7);
+ }
+ if (selectedAppId === 'home' || selectedAppId === 'desktop' || selectedAppId === 'connect') {
+ selectedAppId = place.name;
+ }
+ place.isPlace = true;
+ place.description = selectedAppId;
+ place.activate = () => place.launch();//don't pass any params to launch()
+ if (place.id.startsWith('bookmark:')) {
+ place.uri = place.id.substr(9);
+ place.mimeType = 'inode/directory';
+ place.isDirectory = true;
+ }
+ res.push(place);
+ });
+ res.splice(2, 0, {
+ id: 'special:trash',
+ name: _('Trash'),
+ description: _('Trash'),
+ isPlace: true,
+ activate: () => Util.spawnCommandLine('xdg-open trash:'),
+ iconFactory: (size) => new St.Icon({
+ icon_name: 'user-trash',
+ icon_type: St.IconType.FULLCOLOR,
+ icon_size: size
+ })
+ });
+ res.splice(2, 0, {
+ id: 'special:computer',
+ name: _('Computer'),
+ description: _('Computer'),
+ isPlace: true,
+ activate: () => Util.spawnCommandLine('xdg-open computer:'),
+ iconFactory: (size) => new St.Icon({
+ icon_name: 'computer',
+ icon_type: St.IconType.FULLCOLOR,
+ icon_size: size
+ })
+ });
+
+ return res;
+ }
+
+ searchPlaces(pattern){
+ const places = this.listPlaces();
+ const res = [];
+ places.forEach(place => {
+ const score = searchStr(pattern, place.name);
+ if (score > SEARCH_THRESHOLD) {
+ place.score = score;
+ res.push(place);
+ }
+ });
+
+ return res;
+ }
+
+ listFavoriteFiles() {
+ const res = [];
+ const favorite_infos = XApp.Favorites.get_default().get_favorites(null);
+ favorite_infos.forEach(info => {
+ const found = this.display.appsView.buttonStore.find(button =>
+ button.app.isFavoriteFile && button.app.uri === info.uri);
+ if (found) {
+ res.push(found.app);
+ } else {
+ res.push({
+ name: info.display_name,
+ description: Gio.File.new_for_uri(info.uri).get_path(),
+ gicon: Gio.content_type_get_icon(info.cached_mimetype),
+ isFavoriteFile: true,
+ mimeType: info.cached_mimetype,
+ uri: info.uri
+ });
+ }
+ });
+
+ res.sort( (a, b) => a.name.localeCompare(b.name,
+ undefined,
+ {sensitivity: "base", ignorePunctuation: true}));
+ return res;
+ }
+
+ searchFavoriteFiles(pattern) {
+ const favs = this.listFavoriteFiles();
+ const res = [];
+
+ favs.forEach(item => {
+ const score = searchStr(pattern, item.name);
+ if (item.name && score > SEARCH_THRESHOLD) {
+ item.score = score;
+ res.push(item);
+ }
+ });
+
+ return res;
+ }
+
+ listFolder(folder) {
+ const res = [];
+ const dir = Gio.file_new_for_path(folder);
+ let enumerator;
+ let errorMsg = null;
+ try {
+ enumerator = dir.enumerate_children(
+ 'standard::name,standard::type,standard::icon,standard::content-type,standard::is-hidden',
+ 0, null);
+ } catch(e) {//folder access permission denied probably
+ errorMsg = e.message;
+ }
+ let next;
+ if (enumerator) {
+ next = enumerator.next_file(null);
+ }
+ while (next) {
+ const filename = next.get_name();
+ if (this.settings.showHiddenFiles || !next.get_is_hidden()) {
+ let file = Gio.file_new_for_path(folder + (folder === '/' ? '' : '/') + filename);
+ const isDirectory = next.get_file_type() === Gio.FileType.DIRECTORY;
+ res.push({
+ name: next.get_name(),
+ gicon: next.get_icon(),
+ uri: file.get_uri(),
+ mimeType: next.get_content_type(),
+ isDirectory: isDirectory,
+ description: '',
+ isFolderviewFile: !isDirectory,
+ deleteAfterUse: true
+ });
+ file = null;
+ }
+ next = enumerator.next_file(null);
+ }
+ if (enumerator) {
+ enumerator.close(null);
+ }
+
+ res.sort((a, b) => {
+ if (!a.isDirectory && b.isDirectory) return 1;
+ else if (a.isDirectory && !b.isDirectory) return -1;
+ else if (a.isDirectory && b.isDirectory &&
+ a.name.startsWith('.') && !b.name.startsWith('.')) return 1;
+ else if (a.isDirectory && b.isDirectory &&
+ !a.name.startsWith('.') && b.name.startsWith('.')) return -1;
+ else {
+ const nameA = a.name.toUpperCase();
+ const nameB = b.name.toUpperCase();
+ return (nameA > nameB) ? 1 : ( (nameA < nameB) ? -1 : 0 );
+ }
+ });
+ const parent = dir.get_parent();
+ if (parent) {// Add back button
+ res.unshift({
+ name: 'Back',
+ uri: parent.get_uri(),
+ icon: new St.Icon({
+ icon_name: 'edit-undo-symbolic',
+ icon_type: St.IconType.SYMBOLIC,
+ icon_size: this.settings.appsGridIconSize
+ }),
+ mimeType: 'inode/directory',
+ isBackButton: true,
+ description: '',
+ deleteAfterUse: true
+ });
+ }
+
+ return {results: res, errorMsg: errorMsg};
+ }
+}
+
+class Apps {//This obj provides the .app objects for all the applications categories
+ constructor(appSystem) {
+ this._appsByCategory = {};
+ this._dirs = [];
+ this._knownApps = [];
+ this._appsNeedRefresh = false;
+ this._newInstance = true;
+ this.appSystem = appSystem;
+ this._initAppCategories();
+ }
+
+ installedChanged() {
+ this._appsNeedRefresh = true;
+ }
+
+ _initAppCategories() {
+ const apps_sort = arr => arr.sort((a, b) => a.name.localeCompare(b.name, undefined,
+ {sensitivity: "base", ignorePunctuation: true}));
+ this._dirs = [];
+ this._appsByCategory = {};
+ const iter = this.appSystem.get_tree().get_root_directory().iter();
+ let nextType;
+ while ((nextType = iter.next()) !== CMenu.TreeItemType.INVALID) {
+ if (nextType === CMenu.TreeItemType.DIRECTORY) {
+ const dir = iter.get_directory();
+ if (dir.get_is_nodisplay()) {
+ continue;
+ }
+ const dirId = dir.get_menu_id();
+ const foundApps = this._loadDirectory(dir);
+ if (foundApps.length > 0) {
+ apps_sort(foundApps);
+ this._appsByCategory[dirId] = foundApps;
+ dir.dirId = dirId;
+ this._dirs.push(dir);
+ }
+ }
+ }
+
+ this._dirs.sort((a, b) => {
+ const prefCats = ['ADMINISTRATION', 'PREFERENCES'];
+ const prefIdA = prefCats.indexOf(a.dirId.toUpperCase());
+ const prefIdB = prefCats.indexOf(b.dirId.toUpperCase());
+ if (prefIdA < 0 && prefIdB >= 0) return -1;
+ if (prefIdA >= 0 && prefIdB < 0) return 1;
+ return a.get_name().localeCompare(b.get_name(), undefined,
+ {sensitivity: "base", ignorePunctuation: true});
+ });
+
+ //create "All applications" categories
+ let all = [];
+ let allApps = [];
+ let allSettings = [];
+ Object.keys(this._appsByCategory).forEach(key => {
+ all = all.concat(this._appsByCategory[key]);
+ if (['Preferences','Administration'].includes(key)) {
+ allSettings = allSettings.concat(this._appsByCategory[key]);
+ } else {
+ allApps = allApps.concat(this._appsByCategory[key]);
+ }
+ });
+ this._appsByCategory.all = Array.from(new Set(all));//remove duplicates
+ apps_sort(this._appsByCategory.all);
+ this._appsByCategory.allApps = Array.from(new Set(allApps));//remove duplicates
+ apps_sort(this._appsByCategory.allApps);
+ this._appsByCategory.allSettings = Array.from(new Set(allSettings));//remove duplicates
+ apps_sort(this._appsByCategory.allSettings);
+ //remove apps from allSettings if they're also in allApps
+ this._appsByCategory.allApps.forEach( app => {
+ const i = this._appsByCategory.allSettings.findIndex( a => a === app );
+ if (i > -1) {
+ this._appsByCategory.allSettings.splice(i, 1);
+ }
+ })
+
+ this._appsNeedRefresh = false;
+ this._newInstance = false;
+ }
+
+ _loadDirectory(dir) {
+ let foundApps = [];
+ const iter = dir.iter();
+ let nextType;
+ while ((nextType = iter.next()) !== CMenu.TreeItemType.INVALID) {
+ if (nextType === CMenu.TreeItemType.ENTRY) {
+ const entry = iter.get_entry();
+ const id = entry.get_desktop_file_id();
+ const app = this.appSystem.lookup_app(id);
+ if (!app || app.get_nodisplay()) {
+ continue;
+ }
+
+ foundApps.push(app);
+ app.name = app.get_name();
+ app.description = app.get_description();
+ app.isApplication = true;
+ app.id = id;
+ app.desktop_file_path = entry.get_desktop_file_path();
+
+ if (this._knownApps.indexOf(id) < 0) {//unknown app
+ if (!this._newInstance) {
+ app.newAppShouldHighlight = true;
+ }
+ this._knownApps.push(id);
+ }
+ } else if (nextType === CMenu.TreeItemType.DIRECTORY) {
+ const subDir = iter.get_directory();
+ if (!subDir.get_is_nodisplay()) {
+ foundApps = foundApps.concat(this._loadDirectory(subDir));
+ }
+ }
+ }
+ return foundApps;
+ }
+
+ getDirs() {
+ if (this._appsNeedRefresh) {
+ this._initAppCategories();
+ }
+
+ return this._dirs;
+ }
+
+ dirHasNewApp(dirId) {
+ const apps = this.listApplications(dirId);
+ const newAppIndex = apps.findIndex(app => !!app.newAppShouldHighlight);
+ return (newAppIndex >= 0);
+ }
+
+ listApplications(categoryMenuId) {
+ if (this._appsNeedRefresh) {
+ this._initAppCategories();
+ }
+
+ return this._appsByCategory[categoryMenuId];
+ }
+
+ searchApplications(pattern) {
+ if (!pattern) {
+ return [];
+ }
+
+ const res = [];
+ this.listApplications('all').forEach(app => {
+ const keywords = app.get_keywords() || '';
+ //get and clean up the app ids.
+ let id = app.id.replace('.desktop', '');
+ const idLastDot = id.lastIndexOf('.');
+ if (idLastDot >= 0) {
+ id = id.substring(idLastDot + 1);
+ }
+ id = id.replace('cinnamon-settings-', '');
+
+ const nameScore = searchStr(pattern, app.name);
+ let desScore = searchStr(pattern, app.description);
+ desScore *= 0.95; //slightly lower priority for description match
+ let keywordScore = searchStr(pattern, keywords);
+ keywordScore *= 0.8; //lower priority for keyword match
+ const idScore = searchStr(pattern, id);
+ const bestMatchScore = Math.max(nameScore, desScore, keywordScore, idScore);
+ if (bestMatchScore > SEARCH_THRESHOLD) {
+ app.score = bestMatchScore;
+ res.push(app);
+ }
+ });
+
+ return res;
+ }
+}
+
+class RecentApps {// Simple class to remember the last 20 used apps which are shown in the
+ // "recent" category
+ constructor(appThis) {
+ this.appThis = appThis;
+ }
+
+ add(appId) {
+ const recentApps = this.appThis.settings.recentApps.slice();
+ const duplicate = recentApps.indexOf(appId);
+ if (duplicate > -1) {
+ recentApps.splice(duplicate, 1);
+ }
+ recentApps.unshift(appId);
+ if (recentApps.length > 20) {
+ recentApps.length = 20;
+ }
+ this.appThis.settings.recentApps = recentApps;
+ }
+
+ clear() {
+ this.appThis.settings.recentApps = [];
+ }
+
+ getApps(max_count) {
+ return this.appThis.settings.recentApps.slice(0, max_count);
+ }
+}
+
+function main(metadata, orientation, panel_height, instance_id) {
+ return new CinnamenuApplet(metadata, orientation, panel_height, instance_id);
+}
diff --git a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/appsview.js b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/appsview.js
new file mode 100644
index 0000000000..435ce2bce7
--- /dev/null
+++ b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/appsview.js
@@ -0,0 +1,568 @@
+const Gio = imports.gi.Gio;
+const Gtk = imports.gi.Gtk;
+const Clutter = imports.gi.Clutter;
+const St = imports.gi.St;
+const Atk = imports.gi.Atk;
+const Main = imports.ui.main;
+const Util = imports.misc.util;
+const {SignalManager} = imports.misc.signalManager;
+const {EllipsizeMode} = imports.gi.Pango;
+const {DragMotionResult, makeDraggable} = imports.ui.dnd;
+
+const {
+ wordWrap,
+ getThumbnail_gicon,
+ showTooltip,
+ hideTooltipIfVisible,
+ scrollToButton
+} = require('./utils');
+const DescriptionPlacement = Object.freeze({TOOLTIP: 0, UNDER: 1, NONE: 2});
+
+class AppButton {
+ constructor(appThis, app) {
+ this.appThis = appThis;
+ this.app = app;
+ this.signals = new SignalManager(null);
+
+ //----------ICON---------------------------------------------
+ if (this.app.icon) { //isSearchResult(excl. emoji), isClearRecentsButton, isBackButton
+ this.icon = this.app.icon;
+ } else if (this.app.icon_filename) { //some of isSearchResult
+ const gicon = new Gio.FileIcon({file: Gio.file_new_for_path(this.app.icon_filename)});
+ this.icon = new St.Icon({gicon: gicon, icon_size: this.appThis.settings.appsGridIconSize});
+ } else if (this.app.gicon) { //isRecentFile, isFavoriteFile,
+ //isFolderviewFile/Directory, some of isSearchResult
+ let gicon = this.app.gicon;
+ if (!this.app.isSearchResult) {
+ gicon = getThumbnail_gicon(this.app.uri, this.app.mimeType) || gicon;
+ }
+ this.icon = new St.Icon({gicon: gicon, icon_size: this.appThis.settings.appsGridIconSize});
+ } else if (this.app.emoji) {//emoji search result
+ this.icon = new St.Label({ style: 'color: white; font-size: ' +
+ (Math.round(this.appThis.settings.appsGridIconSize * 0.85)) + 'px;'});
+ this.icon.get_clutter_text().set_text(this.app.emoji);
+ } else if (this.app.isApplication) {//isApplication
+ this.icon = this.app.create_icon_texture(this.appThis.settings.appsGridIconSize);
+ } else if (this.app.iconFactory) {//isPlace
+ this.icon = this.app.iconFactory(this.appThis.settings.appsGridIconSize);
+ if (!this.icon) {
+ this.icon = new St.Icon({icon_name: 'folder', icon_size: this.appThis.settings.appsGridIconSize});
+ }
+ }
+ if (!this.icon) {
+ this.icon = new St.Icon({icon_name: 'dialog-error', icon_size: this.appThis.settings.appsGridIconSize});
+ }
+
+ //--------Label------------------------------------
+ this.label = new St.Label({ style_class: 'menu-application-button-label' });
+ if (this.app.isClearRecentsButton) {
+ this.label.style = 'font-weight: bold;';
+ }
+ //set label text
+ let name = this.app.name.replace(/&/g, '&').replace(/' + name + '';
+ if (this.appThis.settings.descriptionPlacement === DescriptionPlacement.UNDER && description) {
+ markup += '\n' + description + '';
+ }
+ const clutterText = this.label.get_clutter_text();
+ clutterText.set_markup(markup);
+ clutterText.ellipsize = EllipsizeMode.END;
+
+ //-------------actor---------------------
+ this.actor = new St.BoxLayout({
+ vertical: true,
+ reactive: true,
+ accessible_role: Atk.Role.MENU_ITEM
+ });
+
+ this.setGridButtonWidth();
+
+ if (this.icon && this.appThis.settings.appsGridIconSize > 0) {
+ this.actor.add(this.icon, {
+ x_fill: false,
+ y_fill: false,
+ x_align: St.Align.MIDDLE,
+ y_align: St.Align.MIDDLE
+ });
+ }
+ this.actor.add(this.label, {
+ x_fill: false,
+ y_fill: false,
+ x_align: St.Align.MIDDLE,
+ y_align: St.Align.MIDDLE
+ });
+
+ this._setNewAppHighlightClass();
+
+ //----------dnd--------------
+
+ if (this.app.isApplication) {
+ //make apps drag targets (they only act as drag targets when currentCategory is favorite_apps)
+ this.actor._delegate = {
+ handleDragOver: (source) => {
+ if (source.isDraggableApp && source.id !== this.app.id &&
+ this.appThis.currentCategory === 'favorite_apps') {
+ this._resetAllAppsOpacity();
+ this.actor.set_opacity(40);
+ return DragMotionResult.MOVE_DROP;
+ }
+ return DragMotionResult.NO_DROP; },
+ handleDragOut: () => { this.actor.set_opacity(255); },
+ acceptDrop: (source) => {
+ if (source.isDraggableApp && source.id !== this.app.id &&
+ this.appThis.currentCategory === 'favorite_apps') {
+ this.actor.set_opacity(255);
+ this.appThis.addFavoriteAppToPos(source.id, this.app.id);
+ return true;
+ } else {
+ this.actor.set_opacity(255);
+ return DragMotionResult.NO_DROP;
+ } }
+ };
+
+ // make apps draggable.
+ Object.assign(this.actor._delegate, {
+ getDragActorSource: () => this.actor,
+ _getDragActor: () => new Clutter.Clone({source: this.actor}),
+ getDragActor: () => new Clutter.Clone({source: this.icon}),
+ id: this.app.id,
+ get_app_id: () => this.app.id, //used when eg. dragging to panel launcher
+ isDraggableApp: true
+ });
+
+ this.draggable = makeDraggable(this.actor);
+ this.signals.connect(this.draggable, 'drag-begin', () => hideTooltipIfVisible());
+ this.signals.connect(this.draggable, 'drag-end', () => this._resetAllAppsOpacity());
+ }
+
+ //make files and folders draggable
+ if (this.app.isFolderviewFile || this.app.isDirectory || this.app.isRecentFile ) {
+ this.actor._delegate = {
+ handleDragOver: (source) => { return DragMotionResult.NO_DROP; },
+ handleDragOut: () => {},
+ acceptDrop: (source) => { return DragMotionResult.NO_DROP; },
+ getDragActorSource: () => this.actor,
+ _getDragActor: () => new Clutter.Clone({source: this.actor}),
+ getDragActor: () => new Clutter.Clone({source: this.icon}),
+ id: this.app.id,
+ get_app_id: () => this.app.id, //used when eg. dragging to panel launcher
+ isDraggableFile: true,
+ uri: this.app.uri
+ };
+
+ this.draggable = makeDraggable(this.actor);
+ this.signals.connect(this.draggable, 'drag-begin', () => hideTooltipIfVisible());
+ }
+
+ this.signals.connect(this.actor, 'button-release-event', this._handleButtonRelease.bind(this));
+ this.signals.connect(this.actor, 'enter-event', this.handleEnter.bind(this));
+ this.signals.connect(this.actor, 'leave-event', this.handleLeave.bind(this));
+ }
+
+ _setButtonStyleNormal() {
+ this.has_focus = false;
+ this.actor.set_style_class_name('menu-application-button');
+ }
+
+ _setButtonStyleSelected() {
+ this.has_focus = true;
+ this.actor.set_style_class_name('menu-application-button-selected');
+ }
+
+ _setNewAppHighlightClass() {
+ if (this.app.newAppShouldHighlight) {
+ if (!this.actor.has_style_pseudo_class('highlighted')) {
+ this.actor.add_style_pseudo_class('highlighted'); //'font-weight: bold;';
+ }
+ } else {
+ if (this.actor.has_style_pseudo_class('highlighted')) {
+ this.actor.remove_style_pseudo_class('highlighted');
+ }
+ }
+ }
+
+ setGridButtonWidth() {
+ this.actor.width = this.appThis.display.appsView.getGridValues().columnWidth;
+ }
+
+ handleEnter(actor, event) {
+ if (this.appThis.display.contextMenu.isOpen ) {
+ return Clutter.EVENT_PROPAGATE;
+ }
+
+ if (event) {//mouse
+ this.appThis.display.clearFocusedActors();
+ } else {//keyboard navigation
+ scrollToButton(this, this.appThis.settings.enableAnimation);
+ }
+ this._setButtonStyleSelected();
+
+ //------show tooltip
+ if (this.appThis.settings.descriptionPlacement != DescriptionPlacement.TOOLTIP) {
+ return Clutter.EVENT_STOP;
+ }
+
+ let tooltipMarkup = '' + wordWrap(this.app.name) + '';
+ if (this.app.description) {
+ tooltipMarkup += '\n' + wordWrap(this.app.description) + '';
+ }
+ tooltipMarkup = tooltipMarkup.replace(/&/g, '&');
+
+ let [x, y] = this.actor.get_transformed_position();
+ let {width, height} = this.actor;
+
+ x += Math.floor(width / 2);
+ y += height + 8 * global.ui_scale;
+
+ showTooltip(this.actor, x, y, true /*center tooltip on x*/, tooltipMarkup);
+ return Clutter.EVENT_STOP;
+ }
+
+ handleLeave(actor, event) {
+ if (this.appThis.display.contextMenu.isOpen) {
+ return false;
+ }
+ this._setButtonStyleNormal();
+ hideTooltipIfVisible();
+ }
+
+ _handleButtonRelease(actor, event) {
+ const button = event.get_button();
+ if (button === Clutter.BUTTON_PRIMARY) {
+ if (this.appThis.display.contextMenu.isOpen) {
+ this.appThis.display.contextMenu.close();
+ this.appThis.display.clearFocusedActors();
+ this.handleEnter();
+ } else {
+ this.activate(event);
+ }
+ return Clutter.EVENT_STOP;
+ } else if (button === Clutter.BUTTON_SECONDARY) {
+ if (this.appThis.display.contextMenu.isOpen) {
+ this.appThis.display.contextMenu.close();
+ this.appThis.display.clearFocusedActors();
+ this.handleEnter();
+ return Clutter.EVENT_STOP;
+ } else {
+ if (this.app.isApplication || this.app.isFolderviewFile ||
+ this.app.isDirectory || this.app.isFavoriteFile ||
+ this.app.emoji || this.app.isRecentFile){
+ this.openContextMenu(event);
+ }
+ return Clutter.EVENT_STOP;
+ }
+ }
+ return Clutter.EVENT_PROPAGATE;
+ }
+
+ activate() {
+ if (this.app.isApplication) {
+ if (this.app.newAppShouldHighlight) {
+ this.app.newAppShouldHighlight = false;
+ this._setNewAppHighlightClass();
+ }
+ this.appThis.recentApps.add(this.app.id);
+ this.app.open_new_window(-1);
+ this.appThis.menu.close();
+ } else if ((this.app.isDirectory && !this.app.isPlace) || this.app.isBackButton) {
+ this.appThis.setActiveCategory(Gio.File.new_for_uri(this.app.uri).get_path());
+ //don't menu.close()
+ } else if (this.app.isFolderviewFile || this.app.isRecentFile ||
+ this.app.isFavoriteFile || (this.app.isDirectory && this.app.isPlace)) {
+ try {
+ Gio.app_info_launch_default_for_uri(this.app.uri, global.create_app_launch_context());
+ this.appThis.menu.close();
+ } catch (e) {
+ Main.notify(_('Error while opening file:'), e.message);
+ //don't menu.close()
+ }
+ } else if (this.app.isClearRecentsButton) {
+ this.appThis.recentApps.clear();
+ this.appThis.recentManagerDefault.purge_items();
+ this.appThis.setActiveCategory('recents');
+ //don't menu.close
+ } else if (this.app.isSearchResult || this.app.isPlace) {
+ this.app.activate(this.app);
+ this.appThis.menu.close();
+ }
+ }
+
+ openContextMenu(e) {
+ this._setButtonStyleSelected();
+ hideTooltipIfVisible();
+ this.appThis.display.contextMenu.openAppContextMenu(this.app, e, this.actor);
+ }
+
+ _resetAllAppsOpacity() {
+ this.appThis.display.appsView.applicationsGridLayout.get_children().forEach(
+ child => child.set_opacity(255));
+ }
+
+ destroy() {
+ this.signals.disconnectAllSignals();
+ hideTooltipIfVisible();
+
+ this.label.destroy();
+ if (this.icon) {
+ this.icon.destroy();
+ }
+ this.actor.destroy();
+ }
+}
+
+class Subheading {
+ constructor(appThis, subheadingText) {
+ this.appThis = appThis;
+ this.subheadingText = subheadingText;
+ this.signals = new SignalManager(null);
+ this.subheading = new St.Label({
+ text: subheadingText,
+ x_expand: true,
+ reactive: true,
+ accessible_role: Atk.Role.HEADING
+ });
+ this.subheadingBox = new St.BoxLayout({ style_class: 'menu-applications-subheading' });
+ this.subheadingBox.add(this.subheading, {});
+ }
+
+ destroy(){
+ this.signals.disconnectAllSignals();
+ this.subheading.destroy();
+ this.subheadingBox.destroy();
+ }
+}
+
+/* Creates and populates the main applications view. Method populate_add() takes an array of app
+ * objects and creates AppButton objs with .app as a property. These AppButton objs are then stored
+ * in this.buttonStore[] array for later reuse otherwise new AppButton's would need to be created
+ * each time a category is clicked on. Only the .actor property of the AppButton is used to populate
+ * the actual GridLayout*/
+class AppsView {
+ constructor(appThis) {
+ this.appThis = appThis;
+ this.buttonStore = [];
+ this.appsViewSignals = new SignalManager(null);
+
+ this.applicationsGridBox = new St.Bin({
+ style_class: 'menu-applications-grid-box',
+ x_fill: true,
+ y_fill: true
+ });
+ this.applicationsGridLayout = new Clutter.Actor({ layout_manager: new Clutter.GridLayout() });
+ this.applicationsGridBox.set_child(this.applicationsGridLayout);
+ this.headerText = new St.Label({ style_class: 'menu-applications-header-text' });
+ this.applicationsBoxWrapper = new St.BoxLayout({
+ style_class: 'menu-applications-inner-box',
+ vertical: true
+ });
+
+ this.applicationsBoxWrapper.add(this.headerText, { x_fill: false, x_align: St.Align.MIDDLE });
+ this.applicationsBoxWrapper.add(this.applicationsGridBox, {});
+ this.applicationsScrollBox = new St.ScrollView(
+ { style_class: 'vfade menu-applications-scrollbox' });
+ const vscrollApplications = this.applicationsScrollBox.get_vscroll_bar();
+ this.appsViewSignals.connect(vscrollApplications, 'scroll-start',
+ () => { this.appThis.menu.passEvents = true; });
+ this.appsViewSignals.connect(vscrollApplications, 'scroll-stop',
+ () => { this.appThis.menu.passEvents = false; });
+ this.applicationsScrollBox.add_actor(this.applicationsBoxWrapper);
+ this.applicationsScrollBox.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
+ this.applicationsScrollBox.set_clip_to_allocation(true);
+ this.applicationsScrollBox.set_auto_scrolling(this.appThis.settings.enableAutoScroll);
+ this.applicationsScrollBox.set_mouse_scrolling(true);
+ }
+
+ populate(appList, headerText = null) {
+ //too many actors in applicationsGridBox causes display errors, don't know why. Plus, it takes a long time
+ if (appList.length > 1000) {
+ appList.length = 1000; //truncate array
+ headerText = _('Too many entries - showing first 1000 entries only');
+ }
+ this.populate_init(headerText);
+ this.populate_add(appList, null);
+ this.populate_finish();
+ }
+
+ populate_init(headerText = null) {
+ this.clearApps();
+ this.applicationsScrollBox.vscroll.adjustment.set_value(0);//scroll to top
+
+ if (headerText) {
+ this.headerText.set_text(headerText);
+ this.headerText.show();
+ } else {
+ this.headerText.hide();
+ }
+
+ this.column = 0;
+ this.rownum = 0;
+
+ this.subheadings = [];
+ }
+
+ populate_add(appList, subheadingText = null) {
+ const gridLayout = this.applicationsGridLayout.layout_manager;
+
+ if (subheadingText) {
+ if (this.column !== 0) {
+ this.column = 0;
+ this.rownum++;
+ }
+
+ const subheading = new Subheading(this.appThis, subheadingText);
+ gridLayout.attach(
+ subheading.subheadingBox,
+ this.column,
+ this.rownum,
+ this.getGridValues().columns,
+ 1
+ );
+ this.rownum++;
+
+ subheading.subheadingBox.show();
+ this.subheadings.push(subheading);
+ }
+
+ appList.forEach(app => {
+ let appButton = this.buttonStore.find(button => button.app === app);
+
+ if (!appButton) {
+ appButton = new AppButton(this.appThis, app);
+ this.buttonStore.push(appButton);
+ }
+ appButton.setGridButtonWidth();// In case menu has been resized.
+ gridLayout.attach(appButton.actor, this.column, this.rownum, 1, 1);
+ appButton.actor.layout_column = this.column;//used for key navigation
+ this.column++;
+
+ if (this.column > this.getGridValues().columns - 1) {
+ this.column = 0;
+ this.rownum++;
+ }
+
+ appButton._setButtonStyleNormal();
+ });
+ }
+
+ populate_finish() {
+ this.currentGridViewColumnCount = this.getGridValues().columns;
+ }
+
+ focusFirstItem() {
+ const buttons = this.getActiveButtons();
+ if (buttons[0] && !buttons[0].has_focus) {
+ this.appThis.display.clearFocusedActors();
+ buttons[0].handleEnter();
+ }
+ }
+
+ resizeGrid() {
+ this.applicationsGridBox.hide();//for performance
+
+ const newcolumnCount = this.getGridValues().columns;
+ if (this.currentGridViewColumnCount === newcolumnCount) {
+ //Number of columns are the same so just adjust button widths only.
+ this.applicationsGridLayout.get_children().forEach(actor => {
+ if (actor.has_style_class_name('menu-application-button') ||
+ actor.has_style_class_name('menu-application-button-selected')) {
+ actor.width = this.getGridValues().columnWidth;
+ }
+ });
+ } else {//Rearrange buttons to fit new number of columns.
+ this.applicationsGridBox.hide();//
+ const buttons = this.applicationsGridLayout.get_children();
+ this.applicationsGridLayout.remove_all_children();
+ let column = 0;
+ let rownum = 0;
+ const gridLayout = this.applicationsGridLayout.layout_manager;
+ const newColumnWidth = this.getGridValues().columnWidth;
+ buttons.forEach(actor => {
+ if (actor.has_style_class_name('menu-application-button') ||
+ actor.has_style_class_name('menu-application-button-selected')) {
+ actor.width = newColumnWidth;
+ gridLayout.attach(actor, column, rownum, 1, 1);
+ actor.layout_column = column;//used for key navigation
+ column++;
+ if (column > newcolumnCount - 1) {
+ column = 0;
+ rownum++;
+ }
+ } else { //subheading label
+ if (column !== 0) {
+ column = 0;
+ rownum++;
+ }
+ gridLayout.attach(actor, column, rownum, newcolumnCount, 1);
+ rownum++;
+ }
+ });
+ this.applicationsGridBox.show();
+ }
+
+ this.applicationsGridBox.show();
+ this.currentGridViewColumnCount = newcolumnCount;
+ }
+
+ getGridValues() {
+ const gridBoxUsableWidth = this.appThis.display.currentGridBoxUsableWidth;
+ const minColumnWidth = Math.max(140, this.appThis.settings.appsGridIconSize * 1.2);
+ const columns = Math.floor(gridBoxUsableWidth / (minColumnWidth * global.ui_scale));
+ const columnWidth = Math.floor(gridBoxUsableWidth / columns);
+
+ return {columnWidth: columnWidth, columns: columns};
+ }
+
+ getActiveButtons() {
+ const activeButtons = [];
+ this.applicationsGridLayout.get_children().forEach(child => {
+ const foundButton = this.buttonStore.find(button => button.actor === child);
+ if (foundButton) {
+ activeButtons.push(foundButton);
+ }
+ });
+ return activeButtons;
+ }
+
+ clearApps() {
+ this.clearAppsViewFocusedActors();
+
+ //destroy subheading labels
+ if(this.subheadings){
+ this.subheadings.forEach(subheading => subheading.destroy());
+ }
+ this.subheadings = [];
+
+ this.applicationsGridLayout.remove_all_children();
+ }
+
+ clearAppsViewFocusedActors() {
+ this.getActiveButtons().forEach(button => { if (button.has_focus) button.handleLeave(); });
+ }
+
+ buttonStoreCleanup() {
+ //delete all buttons which won't be reused
+ const buttonStore = this.buttonStore.filter(button => {
+ if (button.app.deleteAfterUse) {
+ button.destroy();
+ return false;
+ } else {
+ return true;
+ } });
+ this.buttonStore = buttonStore;
+ }
+
+ destroy() {
+ this.appsViewSignals.disconnectAllSignals();
+ this.headerText.destroy();
+ this.applicationsGridBox.destroy();
+ this.applicationsGridLayout.destroy();
+ this.applicationsBoxWrapper.destroy();
+ this.applicationsScrollBox.destroy();
+ this.buttonStore.forEach(button => { if (button) button.destroy(); });
+ this.buttonStore = [];
+ }
+}
+
+module.exports = {AppsView};
diff --git a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/categoriesview.js b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/categoriesview.js
new file mode 100644
index 0000000000..10da378776
--- /dev/null
+++ b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/categoriesview.js
@@ -0,0 +1,418 @@
+const Gtk = imports.gi.Gtk;
+const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
+const Atk = imports.gi.Atk;
+const Clutter = imports.gi.Clutter;
+const XApp = imports.gi.XApp;
+const St = imports.gi.St;
+const {SignalManager} = imports.misc.signalManager;
+const {DragMotionResult, makeDraggable} = imports.ui.dnd;
+
+const {scrollToButton} = require('./utils');
+
+let buttonTimeoutId = null;
+function clearButtonTimeout() {
+ if (buttonTimeoutId) {
+ clearTimeout(buttonTimeoutId);
+ buttonTimeoutId = null;
+ }
+}
+
+class CategoryButton {
+ constructor(appThis, category_id, category_name, icon_name, gicon) {
+ this.appThis = appThis;
+ this.signals = new SignalManager(null);
+ this.disabled = false;
+ //Note: When option "Activate categories on click" is on, then the this.has_focus === true category
+ //is the one that has keyboard or mouse focus and is not necessarily the same as the currently
+ //selected category (this.appThis.currentCategory)
+ this.has_focus = false;
+ this.id = category_id;
+ this.actor = new St.BoxLayout({
+ style_class: 'menu-category-button',
+ reactive: true,
+ accessible_role: Atk.Role.MENU_ITEM
+ });
+
+ //----icon
+ if (icon_name) {
+ this.icon = new St.Icon({
+ icon_name: icon_name,
+ icon_type: St.IconType.FULLCOLOR,
+ icon_size: this.appThis.settings.categoryIconSize
+ });
+ } else {
+ this.icon = new St.Icon({
+ gicon: gicon,
+ icon_type: St.IconType.FULLCOLOR,
+ icon_size: this.appThis.settings.categoryIconSize
+ });
+ }
+ if (this.appThis.settings.categoryIconSize > 0) {
+ this.actor.add(this.icon, {x_fill: false, y_fill: false, y_align: St.Align.MIDDLE});
+ }
+
+ //---label
+ this.category_name = category_name ? category_name : '';//is this needed?
+ this.label = new St.Label({
+ text: this.category_name,
+ style_class: 'menu-category-button-label'
+ });
+ this.actor.add(this.label, {x_fill: false, y_fill: false, y_align: St.Align.MIDDLE});
+
+ //---dnd
+ this.actor._delegate = {
+ handleDragOver: (source) => {
+ if (!source.isDraggableCategory || source.id === this.id || this.appThis.searchActive) {
+ return DragMotionResult.NO_DROP;
+ }
+ this.appThis.display.categoriesView.resetAllCategoriesOpacity();
+ this.actor.set_opacity(50);
+ return DragMotionResult.MOVE_DROP;
+ },
+ acceptDrop: (source) => {
+ if (!source.isDraggableCategory || source.id === this.id || this.appThis.searchActive) {
+ this.appThis.display.categoriesView.resetAllCategoriesOpacity();
+ return DragMotionResult.NO_DROP;
+ }
+ //move category to new position
+ const categories = this.appThis.settings.categories.slice();
+ const oldIndex = categories.indexOf(source.id);
+ const newIndex = categories.indexOf(this.id);
+ categories.splice(oldIndex, 1);
+ categories.splice(newIndex, 0, source.id);
+ this.appThis.settings.categories = categories;
+ this.appThis.display.categoriesView.resetAllCategoriesOpacity();
+ this.appThis.display.categoriesView.update();
+ this.appThis.setActiveCategory(this.appThis.currentCategory);
+ return true;
+ },
+ getDragActorSource: () => this.actor,
+ _getDragActor: () => new Clutter.Clone({source: this.actor}),
+ getDragActor: () => new Clutter.Clone({source: this.icon}),
+ isDraggableCategory: true,
+ id: this.id
+ };
+ this.draggable = makeDraggable(this.actor);
+
+ // Connect signals
+ this.signals.connect(this.draggable, 'drag-begin', () => this.actor.set_opacity(51));
+ this.signals.connect(this.draggable, 'drag-cancelled', () => this.actor.set_opacity(255));
+ this.signals.connect(this.draggable, 'drag-end', () =>
+ this.appThis.display.categoriesView.resetAllCategoriesOpacity());
+
+ this.signals.connect(this.actor, 'enter-event', this.handleEnter.bind(this));
+ this.signals.connect(this.actor, 'leave-event', this.handleMouseLeave.bind(this));
+ this.signals.connect(this.actor, 'button-release-event', this._handleButtonRelease.bind(this));
+ }
+
+ setHighlight(on) {
+ if (on) {
+ if (!this.actor.has_style_pseudo_class('highlighted')) {
+ this.actor.add_style_pseudo_class('highlighted'); //'font-weight: bold;';
+ }
+ } else {
+ if (this.actor.has_style_pseudo_class('highlighted')) {
+ this.actor.remove_style_pseudo_class('highlighted');
+ }
+ }
+ }
+
+ setButtonStyleNormal() {
+ this.actor.set_style_class_name('menu-category-button');
+ this.icon.set_opacity(255);//undo changes made in _setButtonStyleGreyed()
+ }
+
+ setButtonStyleSelected() {
+ this.actor.set_style_class_name('menu-category-button-selected');
+ }
+
+ _setButtonStyleGreyed() {
+ this.actor.set_style_class_name('menu-category-button-greyed');
+ if (this.icon) {
+ let icon_opacity = this.icon.get_theme_node().get_double('opacity');
+ icon_opacity = Math.min(Math.max(0, icon_opacity), 1);
+ if (icon_opacity) { // Don't set opacity to 0 if not defined
+ this.icon.set_opacity(icon_opacity * 255);
+ }
+ }
+ }
+
+ selectCategory() {
+ this.appThis.setActiveCategory(this.id);
+ }
+
+ handleEnter(actor, event) {
+ //this method handles mouse and key events
+ if (this.has_focus || this.disabled || this.appThis.display.contextMenu.isOpen) {
+ return Clutter.EVENT_PROPAGATE;
+ }
+ //When "activate categories on click" is off, don't enter this button if mouse is moving
+ //quickly towards appviews, i.e. badAngle === true.
+ if (event && !this.appThis.settings.categoryClick && this.appThis.display.badAngle) {
+ clearButtonTimeout();
+ //badAngle now but check again in a short while
+ buttonTimeoutId = setTimeout(
+ () => {
+ this.appThis.display.updateMouseTracking();
+ this.handleEnter(actor, event);
+ },
+ this.appThis.display.TRACKING_TIME
+ );
+ return Clutter.EVENT_PROPAGATE;
+ }
+
+ this.appThis.display.categoriesView.allButtonsRemoveFocusAndHover();
+ this.has_focus = true;
+
+ if (event) {//mouse
+ this.appThis.display.clearFocusedActors();
+ } else {//keypress
+ scrollToButton(this, this.appThis.settings.enableAnimation);
+ }
+
+ // No need to continue if current category is already selected
+ if (this.id === this.appThis.currentCategory) {
+ return Clutter.EVENT_PROPAGATE;
+ }
+ clearButtonTimeout();
+ if (this.appThis.settings.categoryClick) {
+ this.actor.add_style_pseudo_class('hover');
+ } else {
+ this.selectCategory();
+ }
+ return Clutter.EVENT_PROPAGATE;
+ }
+
+ handleMouseLeave(actor, event) {
+ if (this.disabled || this.appThis.display.contextMenu.isOpen) {
+ return false;
+ }
+
+ this.removeFocusAndHover();
+
+ // return focus to currently active category
+ this.appThis.display.categoriesView.setCategoryFocus(this.appThis.currentCategory);
+ }
+
+ removeFocusAndHover() {
+ this.has_focus = false;
+
+ if (this.actor.has_style_pseudo_class('hover')) {
+ this.actor.remove_style_pseudo_class('hover');
+ }
+
+ clearButtonTimeout();
+ }
+
+ _handleButtonRelease(actor, event) {
+ if (this.appThis.display.contextMenu.isOpen) {
+ this.appThis.display.contextMenu.close();
+ return Clutter.EVENT_STOP;
+ }
+ if (this.disabled) {
+ return Clutter.EVENT_STOP;
+ }
+
+ const button = event.get_button();
+ if (button === Clutter.BUTTON_PRIMARY && this.appThis.settings.categoryClick) {
+ this.selectCategory();
+ return Clutter.EVENT_STOP;
+ } else if (button === Clutter.BUTTON_SECONDARY) {
+ if (this.actor.has_style_class_name('menu-category-button-hover')) {
+ //Remove focus from this category button before opening it's context menu.
+ //Todo: Ideally this button should retain focus style to indicate the button the
+ //context menu was opened on.
+ this.removeFocusAndHover();
+ }
+ this.openContextMenu(event);
+ return Clutter.EVENT_STOP;
+ }
+ }
+
+ openContextMenu(e) {
+ this.appThis.display.contextMenu.openCategoryContextMenu(this.id, e, this.actor);
+ }
+
+ disable() {
+ this._setButtonStyleGreyed();
+ this.disabled = true;
+ this.has_focus = false;
+ }
+
+ enable() {
+ this.setButtonStyleNormal();
+ this.disabled = false;
+ }
+
+ destroy() {
+ this.signals.disconnectAllSignals();
+ this.label.destroy();
+ if (this.icon) {
+ this.icon.destroy();
+ }
+ this.actor.destroy();
+ }
+}
+
+/* Creates the categories box and array of CategoryButtons (this.buttons[]). Updates the categories and
+ * populates the categoriesBox. */
+class CategoriesView {
+ constructor(appThis) {
+ this.appThis = appThis;
+ this.buttons = [];
+
+ this.categoriesBox = new St.BoxLayout({ style_class: 'menu-categories-box', vertical: true });
+ this.groupCategoriesWorkspacesWrapper =
+ new St.BoxLayout({/*style: 'max-width: 185px;',*/ vertical: true });
+ this.groupCategoriesWorkspacesWrapper.add(this.categoriesBox, { });
+
+ this.groupCategoriesWorkspacesScrollBox =
+ new St.ScrollView({ style_class: 'vfade menu-categories-scrollbox' });
+ this.groupCategoriesWorkspacesScrollBox.add_actor(this.groupCategoriesWorkspacesWrapper);
+ this.groupCategoriesWorkspacesScrollBox.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER);
+ this.groupCategoriesWorkspacesScrollBox.set_clip_to_allocation(true);
+ this.groupCategoriesWorkspacesScrollBox.set_auto_scrolling(this.appThis.settings.enableAutoScroll);
+ this.groupCategoriesWorkspacesScrollBox.set_mouse_scrolling(true);
+ if (!this.appThis.settings.showCategories) {
+ this.groupCategoriesWorkspacesScrollBox.width = 0;
+ }
+ }
+
+ update() {
+ //Put all enabled categories into newButtons[] in default order by reusing the
+ //buttons in this.buttons[] or by creating new CategoryButton.
+ const newButtons = [];
+
+ //Add 'All applications'
+ let button = this.buttons.find(button => button.id === 'all');
+ if (!button) {
+ button = new CategoryButton(this.appThis, 'all', _('All applications'), 'cinnamon-all-applications', null);
+ }
+ newButtons.push(button);
+
+ //Add other app categories
+ this.appThis.apps.getDirs().forEach(dir => {
+ let button = this.buttons.find(button => button.id === dir.dirId);
+ if (!button) {
+ button = new CategoryButton(this.appThis, dir.dirId, dir.get_name(), null, dir.get_icon());
+ }
+ //highlight category if it contains a new app
+ button.setHighlight(this.appThis.apps.dirHasNewApp(dir.dirId));
+ newButtons.push(button);
+ });
+
+ //Add special categories
+ const enableFavFiles = XApp.Favorites.get_default().get_favorites(null).length > 0;
+ [
+ [enableFavFiles, 'favorite_files', _('Favorites'), 'xapp-user-favorites'],
+ [this.appThis.settings.showPlaces, 'places', _('Places'), 'folder'],
+ [this.appThis.recentsEnabled, 'recents', _('Recent'), 'document-open-recent'],
+ [this.appThis.settings.showFavAppsCategory, 'favorite_apps', _('Favorite apps'), 'emblem-favorite']
+ ].forEach(param => {
+ if (param[0]) {
+ let button = this.buttons.find(button => button.id === param[1]);
+ if (!button) {
+ button = new CategoryButton(this.appThis, param[1], param[2], param[3], null);
+ }
+ newButtons.push(button);
+ }
+ });
+
+ //Add folder categories
+ const folderCategories = this.appThis.settings.folderCategories.slice();
+ let folderCategoriesChanged = false;
+ folderCategories.forEach((folder, index) => {
+ let button = this.buttons.find(button => button.id === folder);
+ if (button) {
+ newButtons.push(button);
+ } else {
+ const file = Gio.file_new_for_path(folder);
+ try {//In case folder no longer exists.
+ const fileInfo = file.query_info('standard::icon', Gio.FileQueryInfoFlags.NONE, null);
+ const gicon = fileInfo.get_icon();
+ let displayName = file.get_basename();
+ if (displayName.length > 19) {
+ displayName = displayName.slice(0,17) + '...';
+ }
+ button = new CategoryButton(this.appThis, folder, displayName, null, gicon);
+ newButtons.push(button);
+ } catch(e) {
+ log("gridmenu:Error creating folder category: " + folder + " ...skipping.");
+ //remove this error causing element from the array.
+ folderCategories.splice(index, 1);
+ folderCategoriesChanged = true;
+ }
+ }
+ });
+ if (folderCategoriesChanged) this.appThis.settings.folderCategories = folderCategories;
+
+ //set user category order to default if none already
+ if (this.appThis.settings.categories.length === 0) {
+ this.appThis.settings.categories = newButtons.map(button => button.id);
+ }
+
+ //add new categories to end of user category order if not already included
+ newButtons.forEach(newButton => {
+ if (this.appThis.settings.categories.indexOf(newButton.id) === -1) {
+ this.appThis.settings.categories.push(newButton.id);
+ }
+ });
+
+ //set this.buttons[] to newButtons[] in user prefered order
+ this.buttons = [];
+ this.appThis.settings.categories.forEach(buttonId => {
+ const foundButton = newButtons.find(newButton => newButton.id === buttonId);
+ if (foundButton && !this.buttons.find(button => button.id === buttonId)) {
+ this.buttons.push(foundButton);
+ }
+ });
+
+ //replace user button order to remove unused ids.
+ if (this.appThis.settings.categories.length > this.buttons.length) {
+ this.appThis.settings.categories = this.buttons.map(button => button.id);
+ }
+
+ //populate categoriesBox with buttons
+ this.categoriesBox.remove_all_children();
+ this.buttons.forEach(button => this.categoriesBox.add_actor(button.actor));
+ }
+
+ setSelectedCategoryStyle(categoryId) {
+ this.buttons.forEach(categoryButton => {
+ if (categoryButton.id === categoryId) {
+ categoryButton.setButtonStyleSelected();
+ } else {
+ categoryButton.setButtonStyleNormal();
+ }
+ });
+ }
+
+ setCategoryFocus(categoryId) {
+ this.buttons.forEach(categoryButton => {
+ if (categoryButton.id === categoryId) {
+ categoryButton.has_focus = true;
+ } else {
+ categoryButton.has_focus = false;
+ }
+ });
+ }
+
+ allButtonsRemoveFocusAndHover() {
+ this.buttons.forEach(button => button.removeFocusAndHover());
+ }
+
+ resetAllCategoriesOpacity() {
+ this.buttons.forEach(button => button.actor.set_opacity(255));
+ }
+
+ destroy() {
+ this.buttons.forEach(button => button.destroy());
+ this.buttons = [];
+ this.categoriesBox.destroy();
+ this.groupCategoriesWorkspacesWrapper.destroy();
+ this.groupCategoriesWorkspacesScrollBox.destroy();
+ }
+}
+
+module.exports = {CategoriesView};
diff --git a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/contextmenu.js b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/contextmenu.js
new file mode 100644
index 0000000000..3e5106f1d5
--- /dev/null
+++ b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/contextmenu.js
@@ -0,0 +1,434 @@
+const Gio = imports.gi.Gio;
+const GLib = imports.gi.GLib;
+const St = imports.gi.St;
+const Clutter = imports.gi.Clutter;
+const Main = imports.ui.main;
+const {PopupBaseMenuItem, PopupMenu, PopupSeparatorMenuItem} = imports.ui.popupMenu;
+const {getUserDesktopDir, changeModeGFile} = imports.misc.fileUtils;
+const {SignalManager} = imports.misc.signalManager;
+const {spawnCommandLine} = imports.misc.util;
+
+const {MODABLE, MODED} = require('./emoji');
+
+class ContextMenuItem extends PopupBaseMenuItem {
+ constructor(appThis, label, iconName, action, insensitive = false) {
+ super({focusOnHover: false});
+ this.appThis = appThis;
+ if (iconName) {
+ const icon = new St.Icon({ style_class: 'popup-menu-icon', icon_name: iconName,
+ icon_type: St.IconType.SYMBOLIC});
+ this.addActor(icon, {span: 0});
+ }
+ this.addActor(new St.Label({text: label}));
+
+ this.signals = new SignalManager(null);
+ this.action = action;
+ if (this.action === null && !insensitive) {//"Open with" item
+ this.actor.add_style_class_name('popup-subtitle-menu-item');
+ } else if (insensitive) {//greyed out item
+ this.actor.add_style_pseudo_class('insensitive');
+ }
+ this.signals.connect(this.actor, 'enter-event', this.handleEnter.bind(this));
+ this.signals.connect(this.actor, 'leave-event', this.handleLeave.bind(this));
+ }
+
+ handleEnter(actor, e) {
+ if (this.action === null) {
+ return Clutter.EVENT_STOP;
+ }
+ this.has_focus = true;
+ this.actor.add_style_pseudo_class('hover');
+ this.actor.add_style_pseudo_class('active');
+ return Clutter.EVENT_STOP;
+ }
+
+ handleLeave(actor, e) {
+ this.has_focus = false;
+ this.actor.remove_style_pseudo_class('hover');
+ this.actor.remove_style_pseudo_class('active');
+ return Clutter.EVENT_STOP;
+ }
+
+ activate(event) {
+ if (!this.action || event && event.get_button() !== Clutter.BUTTON_PRIMARY) {
+ return Clutter.EVENT_STOP;
+ }
+ this.action();
+ return Clutter.EVENT_STOP;
+ }
+
+ destroy() {
+ this.signals.disconnectAllSignals();
+ PopupBaseMenuItem.prototype.destroy.call(this);
+ }
+}
+
+class ContextMenu {
+ constructor(appThis) {
+ this.appThis = appThis;
+ this.menu = new PopupMenu(this.appThis.actor /*,St.Side.TOP*/);
+ this.menu.actor.hide();
+ this.contextMenuBox = new St.BoxLayout({ style_class: '', vertical: true, reactive: true });
+ this.contextMenuBox.add_actor(this.menu.actor);
+
+ this.contextMenuButtons = [];
+ this.isOpen = false;
+ }
+
+ openAppContextMenu(app, e, buttonActor) {
+ //e is used to position context menu at mouse coords. If keypress opens menu then
+ //e is undefined and buttonActor position is used instead.
+ this.contextMenuButtons.forEach(button => button.destroy());
+ this.contextMenuButtons = [];
+
+ //------populate menu
+ if (app.isApplication) {
+ this._populateContextMenu_apps(app);
+ } else if (app.isFolderviewFile || app.isDirectory ||
+ app.isRecentFile || app.isFavoriteFile) {
+ if (!this._populateContextMenu_files(app)) {
+ return;
+ }
+ } else if (app.isSearchResult && app.emoji) {
+ const i = MODABLE.indexOf(app.emoji);//Find if emoji is in list of emoji that can have
+ //skin tone modifiers.
+ if (i < 0) {
+ return;
+ }
+ const addMenuItem = (char, text) => {
+ const newEmoji = MODED[i].replace(/\u{1F3FB}/ug, char); //replace light skin tone character in
+ // MODED[i] with skin tone option.
+ const item = new ContextMenuItem(this.appThis, newEmoji + ' ' + text, null,
+ () => {
+ const clipboard = St.Clipboard.get_default();
+ clipboard.set_text(St.ClipboardType.CLIPBOARD, newEmoji);
+ this.appThis.menu.close();
+ }
+ );
+ this.menu.addMenuItem(item);
+ this.contextMenuButtons.push(item);
+ };
+ addMenuItem('\u{1F3FB}', _('light skin tone'));
+ addMenuItem('\u{1F3FC}', _('medium-light skin tone'));
+ addMenuItem('\u{1F3FD}', _('medium skin tone'));
+ addMenuItem('\u{1F3FE}', _('medium-dark skin tone'));
+ addMenuItem('\u{1F3FF}', _('dark skin tone'));
+ } else {
+ return;
+ }
+ this._showMenu(e, buttonActor);
+ }
+
+ openCategoryContextMenu(categoryId, e, buttonActor) {
+ //e is used to position context menu at mouse coords. If keypress opens menu then
+ //e is undefined and buttonActor position is used instead.
+ this.contextMenuButtons.forEach(button => button.destroy());
+ this.contextMenuButtons = [];
+
+ //------populate menu
+ const addMenuItem = (item) => {
+ this.menu.addMenuItem(item);
+ this.contextMenuButtons.push(item);
+ };
+ if (categoryId.startsWith('/')) {
+ addMenuItem(new ContextMenuItem(this.appThis, _('Remove category'), 'user-trash',
+ () => {
+ if (categoryId === GLib.get_home_dir()) {
+ this.appThis.settings.showHomeFolder = false;
+ this.appThis._onShowHomeFolderChange();
+ } else {
+ this.appThis.removeFolderCategory(categoryId);
+ }
+ this.appThis.display.categoriesView.update();
+ this.close();
+ }
+ ));
+ this.menu.addMenuItem(new PopupSeparatorMenuItem(this.appThis));
+ }
+ addMenuItem(new ContextMenuItem(this.appThis, _('Reset category order'), 'edit-undo-symbolic',
+ () => {
+ this.appThis.settings.categories = [];
+ this.appThis.display.categoriesView.update();
+ this.close();
+ }
+ ));
+
+ this._showMenu(e, buttonActor);
+ }
+
+ _showMenu(e, buttonActor) {
+ //----Position and open context menu----
+ this.isOpen = true;
+ this.appThis.resizer.inhibit_resizing = true;
+
+ const monitor = Main.layoutManager.findMonitorForActor(this.menu.actor);
+ let mx, my;
+ if (e) {
+ [mx, my] = e.get_coords(); //get mouse position
+ } else {//activated by keypress, no e supplied
+ [mx, my] = buttonActor.get_transformed_position();
+ mx += 20;
+ my += 20;
+ }
+ if (mx > monitor.x + monitor.width - this.menu.actor.width) {
+ mx -= this.menu.actor.width;
+ }
+ if (my > monitor.y + monitor.height - this.menu.actor.height - 40/*allow for panel*/) {
+ my -= this.menu.actor.height;
+ }
+
+ let [cx, cy] = this.contextMenuBox.get_transformed_position();
+
+ this.menu.actor.set_anchor_point(Math.round(cx - mx), Math.round(cy - my));
+
+ //This context menu doesn't have an St.Side and so produces errors in .xsession-errors.
+ //Enable animation here for the sole reason that it spams .xsession-errors less. Can't add an
+ //St.Side because in some themes it looks like it should be attached to a panel but isn't.
+ //Ideally, a proper floating popup menu should be coded.
+ this.menu.open(true);
+ return;
+ }
+
+ _populateContextMenu_apps(app) {
+ const addMenuItem = (item) => {
+ this.menu.addMenuItem(item);
+ this.contextMenuButtons.push(item);
+ };
+
+ //Run with NVIDIA GPU
+ if (Main.gpu_offload_supported) {
+ addMenuItem( new ContextMenuItem(this.appThis, _('Run with NVIDIA GPU'), 'cpu',
+ () => {
+ try {
+ app.launch_offloaded(0, [], -1);
+ } catch (e) {
+ logError(e, 'Could not launch app with dedicated gpu: ');
+ }
+ this.appThis.menu.close();
+ }
+ ));
+ }
+
+ //Add to panel
+ addMenuItem(new ContextMenuItem(this.appThis, _('Add to panel'), 'list-add',
+ () => {
+ if (!Main.AppletManager.get_role_provider_exists(Main.AppletManager.Roles.PANEL_LAUNCHER)) {
+ const new_applet_id = global.settings.get_int('next-applet-id');
+ global.settings.set_int('next-applet-id', (new_applet_id + 1));
+ const enabled_applets = global.settings.get_strv('enabled-applets');
+ enabled_applets.push('panel1:right:0:panel-launchers@cinnamon.org:' + new_applet_id);
+ global.settings.set_strv('enabled-applets', enabled_applets);
+ }
+ const launcherApplet =
+ Main.AppletManager.get_role_provider(Main.AppletManager.Roles.PANEL_LAUNCHER);
+ if (launcherApplet) {
+ launcherApplet.acceptNewLauncher(app.id);
+ }
+ this.close();
+ }
+ ));
+
+ //Add to desktop
+ const userDesktopPath = getUserDesktopDir();
+ if (userDesktopPath) {
+ addMenuItem( new ContextMenuItem(this.appThis, _('Add to desktop'), 'computer',
+ () => {
+ const file = Gio.file_new_for_path(app.get_app_info().get_filename());
+ const destFile = Gio.file_new_for_path(userDesktopPath + '/' + file.get_basename());
+ try {
+ file.copy( destFile, 0, null, null);
+ changeModeGFile(destFile, 755);
+ } catch(e) {
+ global.logError('gridmenu: Error creating desktop file', e);
+ }
+ this.close();
+ }
+ ));
+ }
+
+ //add/remove favorite
+ if (this.appThis.appFavorites.isFavorite(app.id)) {
+ addMenuItem( new ContextMenuItem(this.appThis, _('Remove from favorites'), 'starred',
+ () => {
+ this.appThis.appFavorites.removeFavorite(app.id);
+ this.close();
+ }
+ ));
+ } else {
+ addMenuItem( new ContextMenuItem(this.appThis, _('Add to favorites'), 'non-starred',
+ () => {
+ this.appThis.appFavorites.addFavorite(app.id);
+ this.close();
+ }
+ ));
+ }
+
+ //uninstall (Mint only)
+ if (this.appThis._canUninstallApps) {
+ addMenuItem( new ContextMenuItem(this.appThis, _('Uninstall'), 'edit-delete',
+ () => {
+ spawnCommandLine("/usr/bin/cinnamon-remove-application '" +
+ app.get_app_info().get_filename() + "'");
+ this.appThis.menu.close();
+ }
+ ));
+ }
+
+ //show app info
+ if (this.appThis._pamacManagerAvailable) {
+ addMenuItem( new ContextMenuItem(this.appThis, _('App Info'), 'dialog-information',
+ () => { spawnCommandLine("/usr/bin/pamac-manager --details-id=" + app.id);
+ this.appThis.menu.close(); } ));
+ }
+ }
+
+ _populateContextMenu_files(app) {
+ const addMenuItem = (item) => {
+ this.menu.addMenuItem(item);
+ this.contextMenuButtons.push(item);
+ };
+
+ const hasLocalPath = (file) => (file.is_native() && file.get_path() != null);
+ const file = Gio.File.new_for_uri(app.uri);
+ const fileExists = file.query_exists(null);
+ if (!fileExists && !app.isFavoriteFile) {
+ Main.notify(_('This file is no longer available'),'');
+ return false; //no context menu
+ }
+ //Note: a file can be an isFavoriteFile and also not exist so continue below and add option to
+ //remove from favorites.
+
+ //Open with...
+ if (fileExists) {
+ addMenuItem( new ContextMenuItem(this.appThis, _('Open with'), null, null));
+ const defaultInfo = Gio.AppInfo.get_default_for_type(app.mimeType, !hasLocalPath(file));
+ if (defaultInfo) {
+ addMenuItem( new ContextMenuItem(this.appThis, defaultInfo.get_display_name(), null,
+ () => {
+ defaultInfo.launch([file], null);
+ this.appThis.menu.close();
+ }
+ ));
+ }
+ Gio.AppInfo.get_all_for_type(app.mimeType).forEach(info => {
+ if (!hasLocalPath(file) || !info.supports_uris() || info.equal(defaultInfo)) {
+ return;
+ }
+ addMenuItem( new ContextMenuItem(this.appThis, info.get_display_name(), null,
+ () => {
+ info.launch([file], null);
+ this.appThis.menu.close();
+ }
+ ));
+ });
+ addMenuItem( new ContextMenuItem(this.appThis, _('Other application...'), null,
+ () => {
+ spawnCommandLine('nemo-open-with ' + app.uri);
+ this.appThis.menu.close();
+ }
+ ));
+ }
+
+ //add/remove favorite
+ this.menu.addMenuItem(new PopupSeparatorMenuItem(this.appThis));
+ if (this.appThis.xappGetIsFavoriteFile(app.uri)) { //favorite
+ addMenuItem( new ContextMenuItem(this.appThis, _('Remove from favorites'), 'starred',
+ () => {
+ this.appThis.xappRemoveFavoriteFile(app.uri);
+ this.close();
+ }
+ ));
+ } else {
+ addMenuItem( new ContextMenuItem(this.appThis, _('Add to favorites'), 'non-starred',
+ () => {
+ this.appThis.xappAddFavoriteFile(app.uri);
+ this.close();
+ }
+ ));
+ }
+
+ //Add folder as category
+ if (app.isDirectory && this.appThis.settings.showCategories) {
+ const path = Gio.file_new_for_uri(app.uri).get_path();
+ if (!this.appThis.getIsFolderCategory(path)) {
+ this.menu.addMenuItem(new PopupSeparatorMenuItem(this.appThis));
+ addMenuItem(new ContextMenuItem(this.appThis, _('Add folder as category'), 'list-add',
+ () => {
+ if (path === GLib.get_home_dir()) {
+ this.appThis.settings.showHomeFolder = true;
+ }
+ this.appThis.addFolderCategory(path);
+ this.appThis.display.categoriesView.update();
+ this.close();
+ }
+ ));
+ }
+ }
+
+ //Open containing folder
+ const folder = file.get_parent();
+ if (app.isRecentFile || app.isFavoriteFile || app.isFolderviewFile) {
+ this.menu.addMenuItem(new PopupSeparatorMenuItem(this.appThis));
+ addMenuItem(new ContextMenuItem(this.appThis, _('Open containing folder'), 'go-jump',
+ () => {
+ const fileBrowser = Gio.AppInfo.get_default_for_type('inode/directory', true);
+ fileBrowser.launch([folder], null);
+ this.appThis.menu.close();
+ }
+ ));
+ }
+
+ //Move to trash
+ if (!app.isFavoriteFile) {
+ this.menu.addMenuItem(new PopupSeparatorMenuItem(this.appThis));
+
+ const fileInfo = file.query_info('access::can-trash', Gio.FileQueryInfoFlags.NONE, null);
+ const canTrash = fileInfo.get_attribute_boolean('access::can-trash');
+ if (canTrash) {
+ addMenuItem(new ContextMenuItem(this.appThis, _('Move to trash'), 'user-trash',
+ () => {
+ const file = Gio.File.new_for_uri(app.uri);
+ try {
+ file.trash(null);
+ } catch (e) {
+ Main.notify(_('Error while moving file to trash:'), e.message);
+ }
+ this.appThis.setActiveCategory(this.appThis.currentCategory);
+ this.close();
+ }
+ ));
+ } else {//show insensitive item
+ addMenuItem( new ContextMenuItem(this.appThis, _('Move to trash'), 'user-trash',
+ null, true /*insensitive*/));
+ }
+ }
+ return true; //success.
+ }
+
+ getCurrentlyFocusedMenuItem() {
+ if (!this.isOpen) {
+ return -1;
+ }
+
+ let focusedButton = this.contextMenuButtons.findIndex(button => button.has_focus);
+ if (focusedButton < 0) {
+ focusedButton = 0;
+ }
+ return focusedButton;
+ }
+
+ close() {
+ this.menu.close();
+ this.isOpen = false;
+ this.appThis.resizer.inhibit_resizing = false;
+ }
+
+ destroy() {
+ this.contextMenuButtons.forEach(button => button.destroy());
+ this.contextMenuButtons = null;
+ //this.menu.destroy(); //causes errors in .xsession-errors??
+ this.contextMenuBox.destroy();
+ }
+}
+
+module.exports = {ContextMenu};
diff --git a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/display.js b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/display.js
new file mode 100644
index 0000000000..baef65768e
--- /dev/null
+++ b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/display.js
@@ -0,0 +1,317 @@
+const St = imports.gi.St;
+const {SignalManager} = imports.misc.signalManager;
+const {PopupMenuSection} = imports.ui.popupMenu;
+const {ContextMenu} = require('./contextmenu');
+const {AppsView} = require('./appsview');
+const {CategoriesView} = require('./categoriesview');
+const {Sidebar} = require('./sidebar');
+const SidebarPlacement = Object.freeze({TOP: 0, BOTTOM: 1, LEFT: 2, RIGHT: 3});
+
+class Display {
+ constructor (appThis) {
+ this.appThis = appThis;
+ this.displaySignals = new SignalManager(null);
+ const sidebarPlacement = this.appThis.settings.showSidebar ?
+ this.appThis.settings.sidebarPlacement : SidebarPlacement.BOTTOM;
+ switch (sidebarPlacement) {
+ case SidebarPlacement.TOP:
+ this.appThis.menu.setCustomStyleClass('menu-background gridmenu sidebar-top');
+ break;
+ case SidebarPlacement.LEFT:
+ this.appThis.menu.setCustomStyleClass('menu-background gridmenu sidebar-left');
+ break;
+ case SidebarPlacement.BOTTOM:
+ this.appThis.menu.setCustomStyleClass('menu-background gridmenu sidebar-bottom');
+ break;
+ case SidebarPlacement.RIGHT:
+ this.appThis.menu.setCustomStyleClass('menu-background gridmenu sidebar-right');
+ break;
+ }
+ this.sidebar = new Sidebar(this.appThis);
+
+ //==================bottomPane (may also be at the top)================
+ this.searchView = new SearchView(this.appThis);
+ this.displaySignals.connect(
+ this.searchView.searchEntryText,
+ 'text-changed',
+ (...args) => this.appThis._onSearchTextChanged(...args)
+ );
+ this.displaySignals.connect(
+ this.searchView.searchEntryText,
+ 'key-press-event',
+ (...args) => this.appThis._onMenuKeyPress(...args)
+ );
+ this.bottomPane = new St.BoxLayout({});
+ if (sidebarPlacement === SidebarPlacement.TOP || sidebarPlacement === SidebarPlacement.BOTTOM) {
+ this.bottomPane.add(this.sidebar.sidebarOuterBox, {
+ expand: false,
+ x_fill: false,
+ y_fill: false,
+ x_align: St.Align.START,
+ y_align: St.Align.MIDDLE
+ });
+ }
+ this.bottomPane.add(this.searchView.searchBox, {
+ expand: true,
+ x_fill: true,
+ y_fill: false,
+ x_align: St.Align.END,
+ y_align: St.Align.MIDDLE
+ });
+
+ //=================middlePane======================
+ this.appsView = new AppsView(this.appThis);
+ this.categoriesView = new CategoriesView(this.appThis);
+ this.middlePane = new St.BoxLayout({style_class: 'gridmenu-middle-pane'});
+ if (sidebarPlacement === SidebarPlacement.LEFT) {
+ this.middlePane.add(this.sidebar.sidebarOuterBox, {
+ expand: false,
+ x_fill: false,
+ y_fill: false,
+ x_align: St.Align.START,
+ y_align: St.Align.MIDDLE
+ });
+ }
+ this.middlePane.add(this.categoriesView.groupCategoriesWorkspacesScrollBox, {
+ x_fill: false,
+ y_fill: false,
+ x_align: St.Align.START,
+ y_align: St.Align.START
+ });
+ this.middlePane.add(this.appsView.applicationsScrollBox, {
+ x_fill: false,
+ y_fill: false,
+ x_align: St.Align.START,
+ y_align: St.Align.START,
+ expand: false
+ });
+ if (sidebarPlacement === SidebarPlacement.RIGHT) {
+ this.middlePane.add(this.sidebar.sidebarOuterBox, {
+ expand: false,
+ x_fill: false,
+ y_fill: false,
+ x_align: St.Align.START,
+ y_align: St.Align.MIDDLE
+ });
+ }
+
+ //=============mainBox================
+ //set style: 'spacing: 0px' so that extra space is not added to mainBox when contextMenuBox is
+ //added. Only happens with themes that have set a spacing value on this class.
+ this.mainBox = new St.BoxLayout({
+ style_class: 'menu-applications-outer-box',
+ style: 'spacing: 0px;',
+ vertical: true,
+ reactive: true,
+ show_on_set_parent: false
+ });
+ this.mainBox.add_style_class_name('menu-applications-box'); //this is to support old themes
+ if (sidebarPlacement === SidebarPlacement.TOP && this.appThis.settings.showSidebar) {
+ this.mainBox.add(this.bottomPane);
+ }
+ this.mainBox.add_actor(this.middlePane);
+ if (sidebarPlacement !== SidebarPlacement.TOP || !this.appThis.settings.showSidebar) {
+ this.mainBox.add(this.bottomPane);
+ }
+
+ this.contextMenu = new ContextMenu(this.appThis);
+ // Note: The context menu is added to the stage by adding it to mainBox with it's height
+ // set to 0. contextMenuBox is then positioned at mouse coords and above siblings.
+ this.contextMenu.contextMenuBox.height = 0;
+ this.mainBox.add(this.contextMenu.contextMenuBox, {
+ expand: false,
+ x_fill: false,
+ x_align: St.Align.START,
+ y_align: St.Align.MIDDLE
+ });
+
+ //=============menu================
+ const section = new PopupMenuSection();
+ section.actor.add_actor(this.mainBox);
+ this.appThis.menu.addMenuItem(section);
+
+ //if a blank part of the menu was clicked on, close context menu
+ this.displaySignals.connect(this.mainBox, 'button-release-event',() => {
+ if (this.contextMenu.isOpen) {
+ this.contextMenu.close();
+ }
+ });
+
+ //monitor mouse motion to prevent category mis-selection
+ this.categoriesView.categoriesBox.set_reactive(true);
+ this.displaySignals.connect(this.categoriesView.categoriesBox, 'motion-event',
+ () => this.updateMouseTracking());
+
+ this.appsView.applicationsGridBox.show();
+
+ this.mainBox.show();
+ }
+
+ updateMouseTracking() {
+ this.TRACKING_TIME = 70; //ms
+ //keep track of mouse motion to prevent misselection of another category button when moving mouse
+ //pointer from selected category button to app button by calculating angle of pointer movement
+ let [x, y] = global.get_pointer();
+ if (!this.mTrack) {
+ this.mTrack = [];
+ }
+ //compare current position with oldest position in last 0.1 seconds.
+ this.mTrack.push({time: Date.now(), x: x, y: y});//push current position onto array
+ //remove positions older than TRACKING_TIME ago
+ while (this.mTrack[0].time + this.TRACKING_TIME < Date.now()) {
+ this.mTrack.shift();
+ }
+ const dx = x - this.mTrack[0].x;
+ const dy = Math.abs(y - this.mTrack[0].y);
+
+ const tan = dx / dy;
+ if (this.mainBox.get_direction() === St.TextDirection.LTR) {
+ this.badAngle = isFinite(tan) && tan > 0.3;
+ } else {
+ this.badAngle = isFinite(tan) && tan < -0.3;
+ }
+ }
+
+ clearFocusedActors() {
+ if (this.contextMenu.isOpen) {
+ this.contextMenu.close();
+ }
+ this.appsView.clearAppsViewFocusedActors();
+ this.sidebar.clearSidebarFocusedActors();
+ this.categoriesView.allButtonsRemoveFocusAndHover();
+ }
+
+ onMenuResized(userWidth, userHeight){ //resizing callback
+ this.updateMenuSize(userWidth, userHeight);
+ this.appsView.resizeGrid();
+ }
+
+ updateMenuSize(newWidth, newHeight) {
+ //if newWidth & newHeight are not supplied, use current settings values.
+ if (!newWidth) {
+ newWidth = this.appThis.settings.customMenuWidth * global.ui_scale,
+ newHeight= this.appThis.settings.customMenuHeight * global.ui_scale
+ }
+
+ //----------height--------
+ //Note: the stored menu height value is middlePane + bottomPane which is smaller than the
+ //menu's actual height. CategoriesView and sidebar height are not automatically
+ //set because ScrollBox.set_policy Gtk.PolicyType.NEVER pushes other items off the menu
+ let appsHeight = newHeight - this.bottomPane.height;
+ appsHeight = Math.max(appsHeight, 200);//set minimum height
+
+ //---set middlePane actors to appsHeight
+ this.appsView.applicationsScrollBox.height = appsHeight;
+ this.categoriesView.groupCategoriesWorkspacesScrollBox.height = appsHeight;
+
+ if (this.appThis.settings.showSidebar) {
+ //find sidebarOuterBox vertical padding
+ const themeNode = this.sidebar.sidebarOuterBox.get_theme_node();
+ const verticalPadding = Math.max(themeNode.get_length('padding-top') +
+ themeNode.get_length('padding-bottom'),
+ themeNode.get_length('padding') * 2);
+
+ //set sidebarScrollBox height
+ this.sidebar.sidebarScrollBox.set_height(-1);//undo previous set_height()
+ this.sidebar.sidebarScrollBox.set_height(Math.min(appsHeight - verticalPadding,
+ this.sidebar.sidebarScrollBox.height));
+ }
+
+ //------------width-------------
+ //Note: the stored menu width value is less than the menu's actual width because it doesn't
+ //include the outer menuBox padding, margin, etc. appsView width is not set automatically
+ //because I don't know how to determine it's available width in order to calculate number
+ //of columns to use in Clutter.GridLayout
+
+ //find minimum width for categoriesView + sidebar (if present)
+ let leftSideWidth = this.categoriesView.groupCategoriesWorkspacesScrollBox.width;
+ if (this.appThis.settings.sidebarPlacement === SidebarPlacement.LEFT ||
+ this.appThis.settings.sidebarPlacement === SidebarPlacement.RIGHT) {
+ leftSideWidth += this.sidebar.sidebarOuterBox.width;
+ }
+
+ //find minimum width of bottomPane
+ this.searchView.searchEntry.width = 5; //Set to something small so that it gets set to its
+ //minimum value.
+ let bottomPaneMinWidth = 0;
+ if ((this.appThis.settings.sidebarPlacement === SidebarPlacement.TOP ||
+ this.appThis.settings.sidebarPlacement === SidebarPlacement.BOTTOM) &&
+ this.appThis.settings.showSidebar) {
+ bottomPaneMinWidth = this.bottomPane.width;
+ }
+
+ //find minimum menu width
+ const minWidthForAppsView = 200;
+ const minMenuWidth = Math.max(leftSideWidth + minWidthForAppsView, bottomPaneMinWidth);
+
+ //---set applicationsGridBox width.
+ const menuWidth = Math.max(minMenuWidth, newWidth);
+ const appsBoxWidth = Math.floor(menuWidth - leftSideWidth);
+ this.appsView.applicationsGridBox.width = appsBoxWidth;
+ const gridBoxNode = this.appsView.applicationsGridBox.get_theme_node();
+ const gridBoxLRPadding = gridBoxNode.get_padding(St.Side.LEFT) + gridBoxNode.get_padding(St.Side.RIGHT);
+ this.currentGridBoxUsableWidth = appsBoxWidth - gridBoxLRPadding;
+
+ //Don't change settings while resizing to avoid excessive disk writes.
+ if (!this.appThis.resizer.resizingInProgress) {
+ this.appThis.settings.customMenuHeight = newHeight / global.ui_scale;
+ this.appThis.settings.customMenuWidth = menuWidth / global.ui_scale;
+ }
+ }
+
+ destroy() {
+ this.displaySignals.disconnectAllSignals();
+ this.searchView.destroy();
+ this.searchView = null;
+ this.appsView.destroy();
+ this.appsView = null;
+ this.sidebar.destroy();
+ this.sidebar = null;
+ this.categoriesView.destroy();
+ this.categoriesView = null;
+ this.contextMenu.destroy();
+ this.contextMenu = null;
+ this.bottomPane.destroy();
+ this.middlePane.destroy();
+ this.mainBox.destroy();
+ }
+}
+
+class SearchView {
+ constructor(appThis) {
+ this.appThis = appThis;
+ this.searchInactiveIcon = new St.Icon({
+ style_class: 'menu-search-entry-icon',
+ icon_name: 'edit-find'
+ });
+ this.searchActiveIcon = new St.Icon({
+ style_class: 'menu-search-entry-icon',
+ icon_name: 'edit-clear'
+ });
+ this.searchEntry = new St.Entry({ name: 'menu-search-entry', track_hover: true, can_focus: true});
+ this.searchEntryText = this.searchEntry.clutter_text;
+ this.searchEntry.set_primary_icon(this.searchInactiveIcon);
+ this.searchBox = new St.BoxLayout({ style_class: 'menu-search-box' });
+ this.searchBox.add(this.searchEntry, { expand: true });
+ }
+
+ showAndConnectSecondaryIcon() {
+ this.searchEntry.set_secondary_icon(this.searchActiveIcon);
+ this.appThis.signals.connect(this.searchEntry, 'secondary-icon-clicked', () => { //todo
+ this.searchEntryText.set_text('');});
+ }
+
+ hideAndDisconnectSecondaryIcon() {
+ this.searchEntry.set_secondary_icon(null);
+ this.appThis.signals.disconnect('secondary-icon-clicked', this.searchEntry);
+ }
+
+ destroy() {
+ this.searchInactiveIcon.destroy();
+ this.searchActiveIcon.destroy();
+ this.searchEntry.destroy();
+ this.searchBox.destroy();
+ }
+}
+
+module.exports = {Display};
diff --git a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/emoji.js b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/emoji.js
new file mode 100644
index 0000000000..355bb05f12
--- /dev/null
+++ b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/emoji.js
@@ -0,0 +1,8028 @@
+//This list is taken from https://unicode.org/emoji/charts/emoji-list.html
+//Emoji List, v16.0
+
+//Additional keywords are from emojilib (https://github.com/muan/emojilib) License: MIT
+
+const EMOJI = [
+// Smileys & Emotion
+[
+'😀',
+'grinning face',
+'face grin smile happy joy :D'
+],[
+'😃',
+'grinning face with big eyes',
+'face mouth open smile happy joy haha :D :) funny'
+],[
+'😄',
+'grinning face with smiling eyes',
+'eye face mouth open smile happy joy funny haha laugh like :D :)'
+],[
+'😁',
+'beaming face with smiling eyes',
+'eye face grin smile happy joy kawaii'
+],[
+'😆',
+'grinning squinting face',
+'face laugh mouth satisfied smile happy joy lol haha glad XD'
+],[
+'😅',
+'grinning face with sweat',
+'cold face open smile sweat hot happy laugh relief'
+],[
+'🤣',
+'rolling on the floor laughing',
+'face floor laugh rofl rolling rotfl laughing lol haha'
+],[
+'😂',
+'face with tears of joy',
+'face joy laugh tear cry tears weep happy happytears haha'
+],[
+'🙂',
+'slightly smiling face',
+'face smile'
+],[
+'🙃',
+'upside-down face',
+'face upside-down upside down face flipped silly smile'
+],[
+'🫠',
+'melting face',
+'disappear dissolve liquid melt'
+],[
+'😉',
+'winking face',
+'face wink happy mischievous secret ;) smile eye'
+],[
+'😊',
+'smiling face with smiling eyes',
+'blush eye face smile happy flushed crush embarrassed shy joy'
+],[
+'😇',
+'smiling face with halo',
+'angel face fantasy halo innocent heaven'
+],[
+'🥰',
+'smiling face with hearts',
+'adore crush hearts in love face love like affection valentines infatuation'
+],[
+'😍',
+'smiling face with heart-eyes',
+'eye face love smile smiling face with heart eyes like affection valentines infatuation crush heart'
+],[
+'🤩',
+'star-struck',
+'eyes face grinning star starry-eyed star struck smile starry'
+],[
+'😘',
+'face blowing a kiss',
+'face kiss love like affection valentines infatuation'
+],[
+'😗',
+'kissing face',
+'face kiss love like 3 valentines infatuation'
+],[
+'☺️',
+'smiling face',
+'face outlined relaxed smile blush massage happiness'
+],[
+'😚',
+'kissing face with closed eyes',
+'closed eye face kiss love like affection valentines infatuation'
+],[
+'😙',
+'kissing face with smiling eyes',
+'eye face kiss smile affection valentines infatuation'
+],[
+'🥲',
+'smiling face with tear',
+'grateful proud relieved smiling tear touched sad cry pretend'
+],[
+'😋',
+'face savoring food',
+'delicious face savouring smile yum happy joy tongue silly yummy nom'
+],[
+'😛',
+'face with tongue',
+'face tongue prank childish playful mischievous smile'
+],[
+'😜',
+'winking face with tongue',
+'eye face joke tongue wink prank childish playful mischievous smile'
+],[
+'🤪',
+'zany face',
+'eye goofy large small face crazy'
+],[
+'😝',
+'squinting face with tongue',
+'eye face horrible taste tongue prank playful mischievous smile'
+],[
+'🤑',
+'money-mouth face',
+'face money mouth money mouth face rich dollar'
+],[
+'🤗',
+'hugging face',
+'face hug hugging smile'
+],[
+'🤭',
+'face with hand over mouth',
+'whoops shock sudden realization surprise face'
+],[
+'🫢',
+'face with open eyes and hand over mouth',
+'whoops shock sudden realization surprise face'
+],[
+'🫣',
+'face with peeking eye',
+'captivated peep stare'
+],[
+'🤫',
+'shushing face',
+'quiet shush face shhh'
+],[
+'🤔',
+'thinking face',
+'face thinking hmmm think consider'
+],[
+'🫡',
+'saluting face',
+'ok salute sunny troops yes'
+],[
+'🤐',
+'zipper-mouth face',
+'face mouth zipper zipper mouth face sealed secret'
+],[
+'🤨',
+'face with raised eyebrow',
+'distrust skeptic disapproval disbelief mild surprise scepticism face surprise'
+],[
+'😐',
+'neutral face',
+'deadpan face meh neutral indifference :|'
+],[
+'😑',
+'expressionless face',
+'expressionless face inexpressive meh unexpressive indifferent - - deadpan'
+],[
+'😶',
+'face without mouth',
+'face mouth quiet silent hellokitty'
+],[
+'🫥',
+'dotted line face',
+'depressed disappear hide introvert invisible'
+],[
+'😶🌫️',
+'face in clouds',
+'absentminded face in the fog head in clouds'
+],[
+'😏',
+'smirking face',
+'face smirk smile mean prank smug sarcasm'
+],[
+'😒',
+'unamused face',
+'face unamused unhappy indifference bored straight face serious sarcasm unimpressed skeptical dubious side eye'
+],[
+'🙄',
+'face with rolling eyes',
+'eyeroll eyes face rolling frustrated'
+],[
+'😬',
+'grimacing face',
+'face grimace teeth'
+],[
+'😮💨',
+'face exhaling',
+'exhale gasp groan relief whisper whistle'
+],[
+'🤥',
+'lying face',
+'face lie pinocchio'
+],[
+'🫨',
+'shaking face',
+'earthquake face shaking shock vibrate loud fear double'
+],[
+'🙂↔️',
+'head shaking horizontally',
+'head shaking horizontally no face'
+],[
+'🙂↕️',
+'head shaking vertically',
+'head shaking vertically yes nod face agree'
+],[
+'😌',
+'relieved face',
+'face relieved relaxed phew massage happiness'
+],[
+'😔',
+'pensive face',
+'dejected face pensive sad depressed upset'
+],[
+'😪',
+'sleepy face',
+'face sleep tired rest nap'
+],[
+'🤤',
+'drooling face',
+'drooling face'
+],[
+'😴',
+'sleeping face',
+'bed bedtime face good goodnight nap night sleep sleeping tired whatever yawn zzz'
+],[
+'',
+'face with bags under eyes',
+'bags bored exhausted eyes face fatigued late sleepy tired weary'
+],[
+'😷',
+'face with medical mask',
+'cold doctor face mask sick ill disease'
+],[
+'🤒',
+'face with thermometer',
+'face ill sick thermometer temperature cold fever'
+],[
+'🤕',
+'face with head-bandage',
+'bandage face hurt injury face with head bandage injured clumsy'
+],[
+'🤢',
+'nauseated face',
+'face nauseated vomit gross green sick throw up ill'
+],[
+'🤮',
+'face vomiting',
+'sick vomit face'
+],[
+'🤧',
+'sneezing face',
+'face gesundheit sneeze sick allergy'
+],[
+'🥵',
+'hot face',
+'feverish heat stroke hot red-faced sweating face heat red'
+],[
+'🥶',
+'cold face',
+'blue-faced cold freezing frostbite icicles face blue frozen'
+],[
+'🥴',
+'woozy face',
+'dizzy intoxicated tipsy uneven eyes wavy mouth face wavy'
+],[
+'😵',
+'knocked-out face',
+'dead face knocked out dizzy face spent unconscious xox dizzy'
+],[
+'😵💫',
+'face with spiral eyes',
+'dizzy hypnotized spiral trouble whoa'
+],[
+'🤯',
+'exploding head',
+'mind blown shocked face mind blown'
+],[
+'🤠',
+'cowboy hat face',
+'cowboy cowgirl face hat'
+],[
+'🥳',
+'partying face',
+'celebration hat horn party face woohoo'
+],[
+'🥸',
+'disguised face',
+'disguise face glasses incognito nose pretent brows moustache'
+],[
+'😎',
+'smiling face with sunglasses',
+'bright cool face sun sunglasses smile summer beach sunglass'
+],[
+'🤓',
+'nerd face',
+'face geek nerd nerdy dork'
+],[
+'🧐',
+'face with monocle',
+'stuffy wealthy face'
+],[
+'😕',
+'confused face',
+'confused face meh indifference huh weird hmmm :/'
+],[
+'🫤',
+'face with diagonal mouth',
+'disappointed meh skeptical unsure :/'
+],[
+'😟',
+'worried face',
+'face worried concern nervous :('
+],[
+'🙁',
+'slightly frowning face',
+'face frown frowning disappointed sad upset'
+],[
+'☹️',
+'frowning face',
+'face frown sad upset'
+],[
+'😮',
+'face with open mouth',
+'face mouth open sympathy surprise impressed wow whoa :O'
+],[
+'😯',
+'hushed face',
+'face hushed stunned surprised woo shh'
+],[
+'😲',
+'astonished face',
+'astonished face shocked totally xox surprised poisoned'
+],[
+'😳',
+'flushed face',
+'dazed face flushed blush shy flattered'
+],[
+'🥺',
+'pleading face',
+'begging mercy puppy eyes face'
+],[
+'🥹',
+'face holding back tears',
+'angry cry proud resist sad'
+],[
+'😦',
+'frowning face with open mouth',
+'face frown mouth open aw what'
+],[
+'😧',
+'anguished face',
+'anguished face stunned nervous'
+],[
+'😨',
+'fearful face',
+'face fear fearful scared terrified nervous oops huh'
+],[
+'😰',
+'anxious face with sweat',
+'blue cold face rushed sweat nervous'
+],[
+'😥',
+'sad but relieved face',
+'disappointed face relieved whew phew sweat nervous'
+],[
+'😢',
+'crying face',
+'cry face sad tear tears depressed upset'
+],[
+'😭',
+'loudly crying face',
+'cry face sad sob tear tears upset depressed'
+],[
+'😱',
+'face screaming in fear',
+'face fear munch scared scream omg'
+],[
+'😖',
+'confounded face',
+'confounded face confused sick unwell oops :S'
+],[
+'😣',
+'persevering face',
+'face persevere sick no upset oops'
+],[
+'😞',
+'disappointed face',
+'disappointed face sad upset depressed :('
+],[
+'😓',
+'downcast face with sweat',
+'cold face sweat hot sad tired exercise'
+],[
+'😩',
+'weary face',
+'face tired weary sleepy sad frustrated upset'
+],[
+'😫',
+'tired face',
+'face tired sick whine upset frustrated'
+],[
+'🥱',
+'yawning face',
+'bored tired yawn sleepy'
+],[
+'😤',
+'face with steam from nose',
+'face triumph won gas phew proud pride'
+],[
+'😡',
+'enraged face',
+'angry enraged face mad pouting rage red hate despise'
+],[
+'😠',
+'angry face',
+'angry face mad annoyed frustrated'
+],[
+'🤬',
+'face with symbols on mouth',
+'swearing cursing face cussing profanity expletive'
+],[
+'😈',
+'smiling face with horns',
+'face fairy tale fantasy horns smile devil'
+],[
+'👿',
+'angry face with horns',
+'demon devil face fantasy imp angry horns'
+],[
+'💀',
+'skull',
+'death face fairy tale monster dead skeleton creepy'
+],[
+'☠️',
+'skull and crossbones',
+'crossbones death face monster skull poison danger deadly scary pirate evil'
+],[
+'💩',
+'pile of poo',
+'dung face monster poo poop hankey shitface fail turd shit'
+],[
+'🤡',
+'clown face',
+'clown face'
+],[
+'👹',
+'ogre',
+'creature face fairy tale fantasy monster troll red mask halloween scary creepy devil demon japanese'
+],[
+'👺',
+'goblin',
+'creature face fairy tale fantasy monster red evil mask scary creepy japanese'
+],[
+'👻',
+'ghost',
+'creature face fairy tale fantasy monster halloween spooky scary'
+],[
+'👽',
+'alien',
+'creature extraterrestrial face fantasy ufo UFO paul weird outer space'
+],[
+'👾',
+'alien monster',
+'alien creature extraterrestrial face monster ufo game arcade play'
+],[
+'🤖',
+'robot',
+'face monster computer machine bot'
+],[
+'😺',
+'grinning cat',
+'cat face grinning mouth open smile animal cats happy'
+],[
+'😸',
+'grinning cat with smiling eyes',
+'cat eye face grin smile animal cats'
+],[
+'😹',
+'cat with tears of joy',
+'cat face joy tear animal cats haha happy tears'
+],[
+'😻',
+'smiling cat with heart-eyes',
+'cat eye face heart love smile smiling cat with heart eyes animal like affection cats valentines'
+],[
+'😼',
+'cat with wry smile',
+'cat face ironic smile wry animal cats smirk'
+],[
+'😽',
+'kissing cat',
+'cat eye face kiss animal cats'
+],[
+'🙀',
+'weary cat',
+'cat face oh surprised weary animal cats munch scared scream'
+],[
+'😿',
+'crying cat',
+'cat cry face sad tear animal tears weep cats upset'
+],[
+'😾',
+'pouting cat',
+'cat face pouting animal cats'
+],[
+'🙈',
+'see-no-evil monkey',
+'evil face forbidden monkey see see no evil monkey animal nature haha'
+],[
+'🙉',
+'hear-no-evil monkey',
+'evil face forbidden hear monkey hear no evil monkey animal nature'
+],[
+'🙊',
+'speak-no-evil monkey',
+'evil face forbidden monkey speak speak no evil monkey animal nature omg'
+],[
+'💌',
+'love letter',
+'heart letter love mail email like affection envelope valentines'
+],[
+'💘',
+'heart with arrow',
+'arrow cupid love like heart affection valentines'
+],[
+'💝',
+'heart with ribbon',
+'ribbon valentine love valentines'
+],[
+'💖',
+'sparkling heart',
+'excited sparkle love like affection valentines'
+],[
+'💗',
+'growing heart',
+'excited growing nervous pulse like love affection valentines pink'
+],[
+'💓',
+'beating heart',
+'beating heartbeat pulsating love like affection valentines pink heart'
+],[
+'💞',
+'revolving hearts',
+'revolving love like affection valentines'
+],[
+'💕',
+'two hearts',
+'love like affection valentines heart'
+],[
+'💟',
+'heart decoration',
+'heart purple-square love like'
+],[
+'❣️',
+'heart exclamation',
+'exclamation mark punctuation decoration love'
+],[
+'💔',
+'broken heart',
+'break broken sad sorry heart heartbreak'
+],[
+'❤️🔥',
+'heart on fire',
+'burn love lust sacred heart'
+],[
+'❤️🩹',
+'mending heart',
+'healthier improving mending recovering recuperating well'
+],[
+'❤️',
+'red heart',
+'heart love like valentines'
+],[
+'🩷',
+'pink heart',
+'cute heart like love pink friendship affection valentines'
+],[
+'🧡',
+'orange heart',
+'orange love like affection valentines'
+],[
+'💛',
+'yellow heart',
+'yellow love like affection valentines'
+],[
+'💚',
+'green heart',
+'green love like affection valentines'
+],[
+'💙',
+'blue heart',
+'blue love like affection valentines'
+],[
+'🩵',
+'light blue heart',
+'cyan heart light blue teal love like affection valentines'
+],[
+'💜',
+'purple heart',
+'purple love like affection valentines'
+],[
+'🤎',
+'brown heart',
+'brown heart coffee love like'
+],[
+'🖤',
+'black heart',
+'black evil wicked love like'
+],[
+'🩶',
+'grey heart',
+'grey heart silver slate love like affection valentines'
+],[
+'🤍',
+'white heart',
+'heart white pure love like'
+],[
+'💋',
+'kiss mark',
+'kiss lips face love like affection valentines'
+],[
+'💯',
+'hundred points',
+'100 full hundred score perfect numbers century exam quiz test pass'
+],[
+'💢',
+'anger symbol',
+'angry comic mad'
+],[
+'💥',
+'collision',
+'boom comic bomb explode explosion blown'
+],[
+'💫',
+'dizzy',
+'comic star sparkle shoot magic'
+],[
+'💦',
+'sweat droplets',
+'comic splashing sweat water drip oops'
+],[
+'💨',
+'dashing away',
+'comic dash running wind air fast shoo fart smoke puff'
+],[
+'🕳',
+'hole',
+'embarrassing'
+],[
+'💬',
+'speech balloon',
+'balloon bubble comic dialog speech words message talk chatting'
+],[
+'👁️🗨️',
+'eye in speech bubble',
+'eye speech bubble witness info'
+],[
+'🗨️',
+'left speech bubble',
+'dialog speech words message talk chatting'
+],[
+'🗯️',
+'right anger bubble',
+'angry balloon bubble mad caption speech thinking'
+],[
+'💭',
+'thought balloon',
+'balloon bubble comic thought cloud speech thinking dream'
+],[
+'💤',
+'zzz',
+'comic sleep sleepy tired dream'
+],
+
+
+
+// People & Body
+[
+'👋',
+'waving hand',
+'hand wave waving hands gesture goodbye solong farewell hello hi palm'
+],[
+'🤚',
+'raised back of hand',
+'backhand raised fingers'
+],[
+'🖐',
+'hand with fingers splayed',
+'finger hand splayed fingers palm'
+],[
+'✋',
+'raised hand',
+'hand high 5 high five fingers stop highfive palm ban'
+],[
+'🖖',
+'vulcan salute',
+'finger hand spock vulcan fingers star trek'
+],[
+'🫱',
+'rightwards hand',
+'right'
+],[
+'🫲',
+'leftwards hand',
+'left'
+],[
+'🫳',
+'palm down hand',
+'dismiss drop shoo'
+],[
+'🫴',
+'palm up hand',
+'beckon catch come offer'
+],[
+'🫷',
+'leftwards pushing hand',
+'high five leftward pushing hand refuse reject stop wait palm'
+],[
+'🫸',
+'rightwards pushing hand',
+'high five rightward pushing hand refuse reject stop wait palm'
+],[
+'👌',
+'OK hand',
+'hand OK ok hand fingers limbs perfect ok okay'
+],[
+'🤌',
+'pinched fingers',
+'fingers hand gesture interrogation pinched sarcastic size tiny small'
+],[
+'🤏',
+'pinching hand',
+'small amount tiny small size'
+],[
+'✌',
+'victory hand',
+'hand v victory fingers ohyeah peace two'
+],[
+'🤞',
+'crossed fingers',
+'cross finger hand luck good lucky'
+],[
+'🫰',
+'hand with index finger and thumb crossed',
+'expensive heart love money snap cash'
+],[
+'🤟',
+'love-you gesture',
+'hand ILY love you gesture fingers gesture'
+],[
+'🤘',
+'sign of the horns',
+'finger hand horns rock-on fingers evil eye sign of horns rock on'
+],[
+'🤙',
+'call me hand',
+'call hand hands gesture'
+],[
+'👈',
+'backhand index pointing left',
+'backhand finger hand index point direction fingers left'
+],[
+'👉',
+'backhand index pointing right',
+'backhand finger hand index point fingers direction right'
+],[
+'👆',
+'backhand index pointing up',
+'backhand finger hand point up fingers direction'
+],[
+'🖕',
+'middle finger',
+'finger hand fingers rude middle flipping'
+],[
+'👇',
+'backhand index pointing down',
+'backhand down finger hand point fingers direction'
+],[
+'☝',
+'index pointing up',
+'finger hand index point up fingers direction'
+],[
+'🫵',
+'index pointing at the viewer',
+'finger index point you'
+],[
+'👍',
+'thumbs up',
+'+1 hand thumb up thumbsup yes awesome good agree accept cool like'
+],[
+'👎',
+'thumbs down',
+'-1 down hand thumb thumbsdown no dislike'
+],[
+'✊',
+'raised fist',
+'clenched fist hand punch fingers grasp'
+],[
+'👊',
+'oncoming fist',
+'clenched fist hand punch angry violence hit attack'
+],[
+'🤛',
+'left-facing fist',
+'fist leftwards left facing fist hand fistbump'
+],[
+'🤜',
+'right-facing fist',
+'fist rightwards right facing fist hand fistbump'
+],[
+'👏',
+'clapping hands',
+'clap hand hands praise applause congrats yay'
+],[
+'🙌',
+'raising hands',
+'celebration gesture hand hooray raised yea hands'
+],[
+'🫶',
+'heart hands',
+'love'
+],[
+'👐',
+'open hands',
+'hand open fingers butterfly hands'
+],[
+'🤲',
+'palms up together',
+'prayer cupped hands hands gesture cupped'
+],[
+'🤝',
+'handshake',
+'agreement hand meeting shake'
+],[
+'🙏',
+'folded hands',
+'ask hand high 5 high five please pray thanks hope wish namaste highfive'
+],[
+'✍',
+'writing hand',
+'hand write lower left ballpoint pen stationery compose'
+],[
+'💅',
+'nail polish',
+'care cosmetics manicure nail polish beauty finger fashion'
+],[
+'🤳',
+'selfie',
+'camera phone'
+],[
+'💪',
+'flexed biceps',
+'biceps comic flex muscle arm hand summer strong'
+],[
+'🦾',
+'mechanical arm',
+'accessibility prosthetic'
+],[
+'🦿',
+'mechanical leg',
+'accessibility prosthetic'
+],[
+'🦵',
+'leg',
+'kick limb'
+],[
+'🦶',
+'foot',
+'kick stomp'
+],[
+'👂',
+'ear',
+'body face hear sound listen'
+],[
+'🦻',
+'ear with hearing aid',
+'accessibility hard of hearing'
+],[
+'👃',
+'nose',
+'body smell sniff'
+],[
+'🧠',
+'brain',
+'intelligent smart'
+],[
+'🫀',
+'anatomical heart',
+'anatomical cardiology heart organ pulse health heartbeat'
+],[
+'🫁',
+'lungs',
+'breath exhalation inhalation organ respiration breathe'
+],[
+'🦷',
+'tooth',
+'dentist teeth'
+],[
+'🦴',
+'bone',
+'skeleton'
+],[
+'👀',
+'eyes',
+'eye face look watch stalk peek see'
+],[
+'👁️',
+'eye',
+'body face look see watch stare'
+],[
+'👅',
+'tongue',
+'body mouth playful'
+],[
+'👄',
+'mouth',
+'lips kiss'
+],[
+'🫦',
+'biting lip',
+'anxious fear flirting nervous uncomfortable worried sexy'
+],[
+'👶',
+'baby',
+'young child boy girl toddler'
+],[
+'🧒',
+'child',
+'gender-neutral unspecified gender young'
+],[
+'👦',
+'boy',
+'young man male guy teenager'
+],[
+'👧',
+'girl',
+'Virgo young zodiac female woman teenager'
+],[
+'🧑',
+'person',
+'adult gender-neutral unspecified gender'
+],[
+'👱',
+'person: blond hair',
+'blond blond-haired person hair person blond hair hairstyle'
+],[
+'👨',
+'man',
+'adult mustache father dad guy classy sir moustache'
+],[
+'🧔',
+'person: beard',
+'beard person bewhiskered man beard'
+],[
+'🧔♂️',
+'man: beard',
+'beard man'
+],[
+'🧔♀️',
+'woman: beard',
+'beard woman'
+],[
+'👨🦰',
+'man: red hair',
+'adult man red hair man red hair hairstyle'
+],[
+'👨🦱',
+'man: curly hair',
+'adult curly hair man man curly hair hairstyle'
+],[
+'👨🦳',
+'man: white hair',
+'adult man white hair man white hair old elder'
+],[
+'👨🦲',
+'man: bald',
+'adult bald man man bald hairless'
+],[
+'👩',
+'woman',
+'adult female girls lady'
+],[
+'👩🦰',
+'woman: red hair',
+'adult red hair woman woman red hair hairstyle'
+],[
+'🧑🦰',
+'person: red hair',
+'adult gender-neutral person red hair unspecified gender person red hair hairstyle'
+],[
+'👩🦱',
+'woman: curly hair',
+'adult curly hair woman woman curly hair hairstyle'
+],[
+'🧑🦱',
+'person: curly hair',
+'adult curly hair gender-neutral person unspecified gender person curly hair hairstyle'
+],[
+'👩🦳',
+'woman: white hair',
+'adult white hair woman woman white hair old elder'
+],[
+'🧑🦳',
+'person: white hair',
+'adult gender-neutral person unspecified gender white hair person white hair elder old'
+],[
+'👩🦲',
+'woman: bald',
+'adult bald woman woman bald hairless'
+],[
+'🧑🦲',
+'person: bald',
+'adult bald gender-neutral person unspecified gender person bald hairless'
+],[
+'👱♀️',
+'woman: blond hair',
+'blond-haired woman blonde hair woman woman blond hair female girl person'
+],[
+'👱♂️',
+'man: blond hair',
+'blond blond-haired man hair man man blond hair male boy blonde guy person'
+],[
+'🧓',
+'older person',
+'adult gender-neutral old unspecified gender human elder senior'
+],[
+'👴',
+'old man',
+'adult man old human male men elder senior'
+],[
+'👵',
+'old woman',
+'adult old woman human female women lady elder senior'
+],[
+'🙍',
+'person frowning',
+'frown gesture worried'
+],[
+'🙍♂️',
+'man frowning',
+'frowning gesture man male boy sad depressed discouraged unhappy'
+],[
+'🙍♀️',
+'woman frowning',
+'frowning gesture woman female girl sad depressed discouraged unhappy'
+],[
+'🙎',
+'person pouting',
+'gesture pouting upset'
+],[
+'🙎♂️',
+'man pouting',
+'gesture man pouting male boy'
+],[
+'🙎♀️',
+'woman pouting',
+'gesture pouting woman female girl'
+],[
+'🙅',
+'person gesturing NO',
+'forbidden gesture hand prohibited person gesturing no decline'
+],[
+'🙅♂️',
+'man gesturing NO',
+'forbidden gesture hand man prohibited man gesturing no male boy nope'
+],[
+'🙅♀️',
+'woman gesturing NO',
+'forbidden gesture hand prohibited woman woman gesturing no female girl nope'
+],[
+'🙆',
+'person gesturing OK',
+'gesture hand OK person gesturing ok agree'
+],[
+'🙆♂️',
+'man gesturing OK',
+'gesture hand man OK man gesturing ok men boy male blue human'
+],[
+'🙆♀️',
+'woman gesturing OK',
+'gesture hand OK woman woman gesturing ok women girl female pink human'
+],[
+'💁',
+'person tipping hand',
+'hand help information sassy tipping'
+],[
+'💁♂️',
+'man tipping hand',
+'man sassy tipping hand male boy human information'
+],[
+'💁♀️',
+'woman tipping hand',
+'sassy tipping hand woman female girl human information'
+],[
+'🙋',
+'person raising hand',
+'gesture hand happy raised question'
+],[
+'🙋♂️',
+'man raising hand',
+'gesture man raising hand male boy'
+],[
+'🙋♀️',
+'woman raising hand',
+'gesture raising hand woman female girl'
+],[
+'🧏',
+'deaf person',
+'accessibility deaf ear hear'
+],[
+'🧏♂️',
+'deaf man',
+'deaf man accessibility'
+],[
+'🧏♀️',
+'deaf woman',
+'deaf woman accessibility'
+],[
+'🙇',
+'person bowing',
+'apology bow gesture sorry respectiful'
+],[
+'🙇♂️',
+'man bowing',
+'apology bowing favor gesture man sorry male boy'
+],[
+'🙇♀️',
+'woman bowing',
+'apology bowing favor gesture sorry woman female girl'
+],[
+'🤦',
+'person facepalming',
+'disbelief exasperation face palm facepalm disappointed slap hand forehead'
+],[
+'🤦♂️',
+'man facepalming',
+'disbelief exasperation face palm facepalm disappointed slap hand forehead man male boy'
+],[
+'🤦♀️',
+'woman facepalming',
+'disbelief exasperation face palm facepalm disappointed slap hand forehead woman female girl'
+],[
+'🤷',
+'person shrugging',
+'doubt ignorance indifference shrug regardless'
+],[
+'🤷♂️',
+'man shrugging',
+'doubt ignorance indifference man shrug male boy confused indifferent'
+],[
+'🤷♀️',
+'woman shrugging',
+'doubt ignorance indifference shrug woman female girl confused indifferent'
+],[
+'🧑⚕️',
+'health worker',
+'doctor healthcare nurse therapist hospital'
+],[
+'👨⚕️',
+'man health worker',
+'doctor healthcare man nurse therapist human'
+],[
+'👩⚕️',
+'woman health worker',
+'doctor healthcare nurse therapist woman human'
+],[
+'🧑🎓',
+'student',
+'graduate learn'
+],[
+'👨🎓',
+'man student',
+'graduate man student human'
+],[
+'👩🎓',
+'woman student',
+'graduate student woman human'
+],[
+'🧑🏫',
+'teacher',
+'instructor professor'
+],[
+'👨🏫',
+'man teacher',
+'instructor man professor teacher human'
+],[
+'👩🏫',
+'woman teacher',
+'instructor professor teacher woman human'
+],[
+'🧑⚖️',
+'judge',
+'justice scales law'
+],[
+'👨⚖️',
+'man judge',
+'judge justice man scales court human'
+],[
+'👩⚖️',
+'woman judge',
+'judge justice scales woman court human'
+],[
+'🧑🌾',
+'farmer',
+'gardener rancher crops'
+],[
+'👨🌾',
+'man farmer',
+'farmer gardener man rancher human'
+],[
+'👩🌾',
+'woman farmer',
+'farmer gardener rancher woman human'
+],[
+'🧑🍳',
+'cook',
+'chef food kitchen culinary'
+],[
+'👨🍳',
+'man cook',
+'chef cook man human'
+],[
+'👩🍳',
+'woman cook',
+'chef cook woman human'
+],[
+'🧑🔧',
+'mechanic',
+'electrician plumber tradesperson worker technician'
+],[
+'👨🔧',
+'man mechanic',
+'electrician man mechanic plumber tradesperson human wrench'
+],[
+'👩🔧',
+'woman mechanic',
+'electrician mechanic plumber tradesperson woman human wrench'
+],[
+'🧑🏭',
+'factory worker',
+'assembly factory industrial worker labor'
+],[
+'👨🏭',
+'man factory worker',
+'assembly factory industrial man worker human'
+],[
+'👩🏭',
+'woman factory worker',
+'assembly factory industrial woman worker human'
+],[
+'🧑💼',
+'office worker',
+'architect business manager white-collar'
+],[
+'👨💼',
+'man office worker',
+'architect business man manager white-collar human'
+],[
+'👩💼',
+'woman office worker',
+'architect business manager white-collar woman human'
+],[
+'🧑🔬',
+'scientist',
+'biologist chemist engineer physicist chemistry'
+],[
+'👨🔬',
+'man scientist',
+'biologist chemist engineer man physicist scientist human'
+],[
+'👩🔬',
+'woman scientist',
+'biologist chemist engineer physicist scientist woman human'
+],[
+'🧑💻',
+'technologist',
+'coder developer inventor software computer'
+],[
+'👨💻',
+'man technologist',
+'coder developer inventor man software technologist engineer programmer human laptop computer'
+],[
+'👩💻',
+'woman technologist',
+'coder developer inventor software technologist woman engineer programmer human laptop computer'
+],[
+'🧑🎤',
+'singer',
+'actor entertainer rock star song artist performer'
+],[
+'👨🎤',
+'man singer',
+'actor entertainer man rock singer star rockstar human'
+],[
+'👩🎤',
+'woman singer',
+'actor entertainer rock singer star woman rockstar human'
+],[
+'🧑🎨',
+'artist',
+'palette painting draw creativity'
+],[
+'👨🎨',
+'man artist',
+'artist man palette painter human'
+],[
+'👩🎨',
+'woman artist',
+'artist palette woman painter human'
+],[
+'🧑✈️',
+'pilot',
+'plane fly airplane'
+],[
+'👨✈️',
+'man pilot',
+'man pilot plane aviator human'
+],[
+'👩✈️',
+'woman pilot',
+'pilot plane woman aviator human'
+],[
+'🧑🚀',
+'astronaut',
+'rocket outerspace'
+],[
+'👨🚀',
+'man astronaut',
+'astronaut man rocket space human'
+],[
+'👩🚀',
+'woman astronaut',
+'astronaut rocket woman space human'
+],[
+'🧑🚒',
+'firefighter',
+'firetruck fire'
+],[
+'👨🚒',
+'man firefighter',
+'firefighter firetruck man fireman human'
+],[
+'👩🚒',
+'woman firefighter',
+'firefighter firetruck woman fireman human'
+],[
+'👮',
+'police officer',
+'cop officer police'
+],[
+'👮♂️',
+'man police officer',
+'cop man officer police law legal enforcement arrest 911'
+],[
+'👮♀️',
+'woman police officer',
+'cop officer police woman law legal enforcement arrest 911 female'
+],[
+'🕵',
+'detective',
+'sleuth spy human'
+],[
+'🕵️♂️',
+'man detective',
+'detective man sleuth spy crime'
+],[
+'🕵️♀️',
+'woman detective',
+'detective sleuth spy woman human female'
+],[
+'💂',
+'guard',
+'protect'
+],[
+'💂♂️',
+'man guard',
+'guard man uk gb british male guy royal'
+],[
+'💂♀️',
+'woman guard',
+'guard woman uk gb british female royal'
+],[
+'🥷',
+'ninja',
+'fighter hidden stealth ninjutsu skills japanese'
+],[
+'👷',
+'construction worker',
+'construction hat worker labor build'
+],[
+'👷♂️',
+'man construction worker',
+'construction man worker male human wip guy build labor'
+],[
+'👷♀️',
+'woman construction worker',
+'construction woman worker female human wip build labor'
+],[
+'🫅',
+'person with crown',
+'monarch noble regal royalty king queen'
+],[
+'🤴',
+'prince',
+'boy man male crown royal king'
+],[
+'👸',
+'princess',
+'fairy tale fantasy girl woman female blond crown royal queen'
+],[
+'👳',
+'person wearing turban',
+'turban headdress'
+],[
+'👳♂️',
+'man wearing turban',
+'man turban male indian hinduism arabs'
+],[
+'👳♀️',
+'woman wearing turban',
+'turban woman female indian hinduism arabs'
+],[
+'👲',
+'person with skullcap',
+'cap gua pi mao hat person skullcap man with skullcap male boy chinese'
+],[
+'🧕',
+'woman with headscarf',
+'headscarf hijab mantilla tichel bandana head kerchief female'
+],[
+'🤵',
+'person in tuxedo',
+'groom person tuxedo man in tuxedo couple marriage wedding'
+],[
+'🤵♂️',
+'man in tuxedo',
+'man tuxedo formal fashion'
+],[
+'🤵♀️',
+'woman in tuxedo',
+'tuxedo woman formal fashion'
+],[
+'👰',
+'person with veil',
+'bride person veil wedding bride with veil couple marriage woman'
+],[
+'👰♂️',
+'man with veil',
+'man veil wedding marriage'
+],[
+'👰♀️',
+'woman with veil',
+'veil woman wedding marriage'
+],[
+'🤰',
+'pregnant woman',
+'belly full pregnant woman baby'
+],[
+'🫃',
+'pregnant man',
+'belly full pregnant'
+],[
+'🫄',
+'pregnant person',
+'belly full pregnant'
+],[
+'🤱',
+'breast-feeding',
+'baby breast nursing breast feeding'
+],[
+'👩🍼',
+'woman feeding baby',
+'baby feeding nursing woman birth food'
+],[
+'👨🍼',
+'man feeding baby',
+'baby feeding man nursing birth food'
+],[
+'🧑🍼',
+'person feeding baby',
+'baby feeding nursing person birth food'
+],[
+'👼',
+'baby angel',
+'angel baby face fairy tale fantasy heaven wings halo'
+],[
+'🎅',
+'Santa Claus',
+'celebration Christmas claus father santa santa claus festival man male xmas father christmas'
+],[
+'🤶',
+'Mrs. Claus',
+'celebration Christmas claus mother Mrs. mrs claus woman female xmas mother christmas'
+],[
+'🧑🎄',
+'mx claus',
+'Claus, christmas christmas'
+],[
+'🦸',
+'superhero',
+'good hero heroine superpower marvel'
+],[
+'🦸♂️',
+'man superhero',
+'good hero man superpower male superpowers'
+],[
+'🦸♀️',
+'woman superhero',
+'good hero heroine superpower woman female superpowers'
+],[
+'🦹',
+'supervillain',
+'criminal evil superpower villain marvel'
+],[
+'🦹♂️',
+'man supervillain',
+'criminal evil man superpower villain male bad hero superpowers'
+],[
+'🦹♀️',
+'woman supervillain',
+'criminal evil superpower villain woman female bad heroine superpowers'
+],[
+'🧙',
+'mage',
+'sorcerer sorceress witch wizard magic'
+],[
+'🧙♂️',
+'man mage',
+'sorcerer wizard man male mage'
+],[
+'🧙♀️',
+'woman mage',
+'sorceress witch woman female mage'
+],[
+'🧚',
+'fairy',
+'Oberon Puck Titania wings magical'
+],[
+'🧚♂️',
+'man fairy',
+'Oberon Puck man male'
+],[
+'🧚♀️',
+'woman fairy',
+'Titania woman female'
+],[
+'🧛',
+'vampire',
+'Dracula undead blood twilight'
+],[
+'🧛♂️',
+'man vampire',
+'Dracula undead man male dracula'
+],[
+'🧛♀️',
+'woman vampire',
+'undead woman female'
+],[
+'🧜',
+'merperson',
+'mermaid merman merwoman sea'
+],[
+'🧜♂️',
+'merman',
+'Triton man male triton'
+],[
+'🧜♀️',
+'mermaid',
+'merwoman woman female ariel'
+],[
+'🧝',
+'elf',
+'magical LOTR style'
+],[
+'🧝♂️',
+'man elf',
+'magical man male'
+],[
+'🧝♀️',
+'woman elf',
+'magical woman female'
+],[
+'🧞',
+'genie',
+'djinn (non-human color) magical wishes'
+],[
+'🧞♂️',
+'man genie',
+'djinn man male'
+],[
+'🧞♀️',
+'woman genie',
+'djinn woman female'
+],[
+'🧟',
+'zombie',
+'undead walking dead (non-human color) dead'
+],[
+'🧟♂️',
+'man zombie',
+'undead walking dead man male dracula'
+],[
+'🧟♀️',
+'woman zombie',
+'undead walking dead woman female'
+],[
+'🧌',
+'troll',
+'fairy tale fantasy monster ogre'
+],[
+'💆',
+'person getting massage',
+'face massage salon relax'
+],[
+'💆♂️',
+'man getting massage',
+'face man massage male boy head'
+],[
+'💆♀️',
+'woman getting massage',
+'face massage woman female girl head'
+],[
+'💇',
+'person getting haircut',
+'barber beauty haircut parlor hairstyle'
+],[
+'💇♂️',
+'man getting haircut',
+'haircut man male boy'
+],[
+'💇♀️',
+'woman getting haircut',
+'haircut woman female girl'
+],[
+'🚶',
+'person walking',
+'hike walking move human feet steps'
+],[
+'🚶♂️',
+'man walking',
+'hike man walking move human feet steps'
+],[
+'🚶♀️',
+'woman walking',
+'hike walking woman move human feet steps female'
+],[
+'🚶➡️',
+'person walking facing right',
+'hike man walking move human feet steps'
+],[
+'🚶♂️➡️',
+'man walking facing right',
+'hike man walking move human feet steps'
+],[
+'🚶♀️➡️',
+'woman walking facing right',
+'hike walking woman move human feet steps female'
+],[
+'🧍',
+'person standing',
+'stand standing still'
+],[
+'🧍♂️',
+'man standing',
+'man standing still'
+],[
+'🧍♀️',
+'woman standing',
+'standing woman still'
+],[
+'🧎',
+'person kneeling',
+'person kneeling pray respectful'
+],[
+'🧎♂️',
+'man kneeling',
+'kneeling man pray respectful'
+],[
+'🧎♀️',
+'woman kneeling',
+'kneeling woman respectful pray'
+],[
+'🧎➡️',
+'person kneeling facing right',
+'person kneeling pray respectful'
+],[
+'🧎♀️➡️',
+'woman kneeling facing right',
+'kneeling woman respectful pray'
+],[
+'🧎♂️➡️',
+'man kneeling facing right',
+'kneeling man pray respectful'
+],[
+'🧑🦯',
+'person with white cane',
+'accessibility blind person with probing cane'
+],[
+'🧑🦯➡️',
+'person with white cane facing right',
+'accessibility blind person with probing cane'
+],[
+'👨🦯',
+'man with white cane',
+'accessibility blind man with probing cane'
+],[
+'👨🦯➡️',
+'man with white cane facing right',
+'accessibility blind man with probing cane'
+],[
+'👩🦯',
+'woman with white cane',
+'accessibility blind woman with probing cane'
+],[
+'👩🦯➡️',
+'woman with white cane facing right',
+'accessibility blind woman with probing cane'
+],[
+'🧑🦼',
+'person in motorized wheelchair',
+'accessibility wheelchair disability'
+],[
+'🧑🦼➡️',
+'person in motorized wheelchair facing right',
+'accessibility wheelchair disability'
+],[
+'👨🦼',
+'man in motorized wheelchair',
+'accessibility man wheelchair disability'
+],[
+'👨🦼➡️',
+'man in motorized wheelchair facing right',
+'accessibility man wheelchair disability'
+],[
+'👩🦼',
+'woman in motorized wheelchair',
+'accessibility wheelchair woman disability'
+],[
+'👩🦼➡️',
+'woman in motorized wheelchair facing right',
+'accessibility wheelchair woman disability'
+],[
+'🧑🦽',
+'person in manual wheelchair',
+'accessibility wheelchair disability'
+],[
+'🧑🦽➡️',
+'person in manual wheelchair facing right',
+'accessibility wheelchair disability'
+],[
+'👨🦽',
+'man in manual wheelchair',
+'accessibility man wheelchair disability'
+],[
+'👨🦽➡️',
+'man in manual wheelchair facing right',
+'accessibility man wheelchair disability'
+],[
+'👩🦽',
+'woman in manual wheelchair',
+'accessibility wheelchair woman disability'
+],[
+'👩🦽➡️',
+'woman in manual wheelchair facing right',
+'accessibility wheelchair woman disability'
+],[
+'🏃',
+'person running',
+'marathon racing running sprint move fast exercise race'
+],[
+'🏃♂️',
+'man running',
+'man marathon racing running sprint move fast exercise race male'
+],[
+'🏃♀️',
+'woman running',
+'marathon racing running sprint woman move fast exercise race female'
+],[
+'🏃➡️',
+'person running facing right',
+'marathon running sprint move fast exercise race'
+],[
+'🏃♀️➡️',
+'woman running facing right',
+'marathon racing running sprint woman move fast exercise race female'
+],[
+'🏃♂️➡️',
+'man running facing right',
+'man marathon racing running sprint move fast exercise race male'
+],[
+'💃',
+'woman dancing',
+'dance dancing woman female girl fun'
+],[
+'🕺',
+'man dancing',
+'dance dancing man male boy fun dancer'
+],[
+'🕴',
+'person in suit levitating',
+'business person suit man in suit levitating levitate hover jump'
+],[
+'👯',
+'people with bunny ears',
+'bunny ear dancer partying perform costume'
+],[
+'👯♂️',
+'men with bunny ears',
+'bunny ear dancer men partying male bunny boys'
+],[
+'👯♀️',
+'women with bunny ears',
+'bunny ear dancer partying women female bunny girls'
+],[
+'🧖',
+'person in steamy room',
+'sauna steam room hamam steambath relax spa'
+],[
+'🧖♂️',
+'man in steamy room',
+'sauna steam room male man spa steamroom'
+],[
+'🧖♀️',
+'woman in steamy room',
+'sauna steam room female woman spa steamroom'
+],[
+'🧗',
+'person climbing',
+'climber sport'
+],[
+'🧗♂️',
+'man climbing',
+'climber sports hobby man male rock'
+],[
+'🧗♀️',
+'woman climbing',
+'climber sports hobby woman female rock'
+],[
+'🤺',
+'person fencing',
+'fencer fencing sword sports'
+],[
+'🏇',
+'horse racing',
+'horse jockey racehorse racing animal betting competition gambling luck'
+],[
+'⛷️',
+'skier',
+'ski snow sports winter'
+],[
+'🏂',
+'snowboarder',
+'ski snow snowboard sports winter'
+],[
+'🏌',
+'person golfing',
+'ball golf sports business'
+],[
+'🏌️♂️',
+'man golfing',
+'golf man sport'
+],[
+'🏌️♀️',
+'woman golfing',
+'golf woman sports business female'
+],[
+'🏄',
+'person surfing',
+'surfing sport sea'
+],[
+'🏄♂️',
+'man surfing',
+'man surfing sports ocean sea summer beach'
+],[
+'🏄♀️',
+'woman surfing',
+'surfing woman sports ocean sea summer beach female'
+],[
+'🚣',
+'person rowing boat',
+'boat rowboat sport move'
+],[
+'🚣♂️',
+'man rowing boat',
+'boat man rowboat sports hobby water ship'
+],[
+'🚣♀️',
+'woman rowing boat',
+'boat rowboat woman sports hobby water ship female'
+],[
+'🏊',
+'person swimming',
+'swim sport pool'
+],[
+'🏊♂️',
+'man swimming',
+'man swim sports exercise human athlete water summer'
+],[
+'🏊♀️',
+'woman swimming',
+'swim woman sports exercise human athlete water summer female'
+],[
+'⛹',
+'person bouncing ball',
+'ball sports human'
+],[
+'⛹️♂️',
+'man bouncing ball',
+'ball man sport'
+],[
+'⛹️♀️',
+'woman bouncing ball',
+'ball woman sports human female'
+],[
+'🏋',
+'person lifting weights',
+'lifter weight sports training exercise'
+],[
+'🏋️♂️',
+'man lifting weights',
+'man weight lifter sport'
+],[
+'🏋️♀️',
+'woman lifting weights',
+'weight lifter woman sports training exercise female'
+],[
+'🚴',
+'person biking',
+'bicycle biking cyclist sport move'
+],[
+'🚴♂️',
+'man biking',
+'bicycle biking cyclist man sports bike exercise hipster'
+],[
+'🚴♀️',
+'woman biking',
+'bicycle biking cyclist woman sports bike exercise hipster female'
+],[
+'🚵',
+'person mountain biking',
+'bicycle bicyclist bike cyclist mountain sport move'
+],[
+'🚵♂️',
+'man mountain biking',
+'bicycle bike cyclist man mountain transportation sports human race'
+],[
+'🚵♀️',
+'woman mountain biking',
+'bicycle bike biking cyclist mountain woman transportation sports human race female'
+],[
+'🤸',
+'person cartwheeling',
+'cartwheel gymnastics sport gymnastic'
+],[
+'🤸♂️',
+'man cartwheeling',
+'cartwheel gymnastics man'
+],[
+'🤸♀️',
+'woman cartwheeling',
+'cartwheel gymnastics woman'
+],[
+'🤼',
+'people wrestling',
+'wrestle wrestler sport'
+],[
+'🤼♂️',
+'men wrestling',
+'men wrestle sports wrestlers'
+],[
+'🤼♀️',
+'women wrestling',
+'women wrestle sports wrestlers'
+],[
+'🤽',
+'person playing water polo',
+'polo water sport'
+],[
+'🤽♂️',
+'man playing water polo',
+'man water polo sports pool'
+],[
+'🤽♀️',
+'woman playing water polo',
+'water polo woman sports pool'
+],[
+'🤾',
+'person playing handball',
+'ball handball sport'
+],[
+'🤾♂️',
+'man playing handball',
+'handball man sports'
+],[
+'🤾♀️',
+'woman playing handball',
+'handball woman sports'
+],[
+'🤹',
+'person juggling',
+'balance juggle multitask skill performance'
+],[
+'🤹♂️',
+'man juggling',
+'juggling man multitask juggle balance skill'
+],[
+'🤹♀️',
+'woman juggling',
+'juggling multitask woman juggle balance skill'
+],[
+'🧘',
+'person in lotus position',
+'meditation yoga serenity meditate'
+],[
+'🧘♂️',
+'man in lotus position',
+'meditation yoga man male serenity zen mindfulness'
+],[
+'🧘♀️',
+'woman in lotus position',
+'meditation yoga woman female serenity zen mindfulness'
+],[
+'🛀',
+'person taking bath',
+'bath bathtub clean shower bathroom'
+],[
+'🛌',
+'person in bed',
+'hotel sleep bed rest'
+],[
+'🧑🤝🧑',
+'people holding hands',
+'couple hand hold holding hands person friendship'
+],[
+'👭',
+'women holding hands',
+'couple hand holding hands women pair friendship love like female people human'
+],[
+'👫',
+'woman and man holding hands',
+'couple hand hold holding hands man woman pair people human love date dating like affection valentines marriage'
+],[
+'👬',
+'men holding hands',
+'couple Gemini holding hands man men twins zodiac pair love like bromance friendship people human'
+],[
+'💏',
+'kiss',
+'couple pair valentines love like dating marriage'
+],[
+'👩❤️💋👨',
+'kiss: woman, man',
+'couple kiss man woman kiss woman man love'
+],[
+'👨❤️💋👨',
+'kiss: man, man',
+'couple kiss man kiss man man pair valentines love like dating marriage'
+],[
+'👩❤️💋👩',
+'kiss: woman, woman',
+'couple kiss woman kiss woman woman pair valentines love like dating marriage'
+],[
+'💑',
+'couple with heart',
+'couple love pair like affection human dating valentines marriage'
+],[
+'👩❤️👨',
+'couple with heart: woman, man',
+'couple couple with heart love man woman couple with heart woman man'
+],[
+'👨❤️👨',
+'couple with heart: man, man',
+'couple couple with heart love man couple with heart man man pair like affection human dating valentines marriage'
+],[
+'👩❤️👩',
+'couple with heart: woman, woman',
+'couple couple with heart love woman couple with heart woman woman pair like affection human dating valentines marriage'
+],[
+'👨👩👦',
+'family: man, woman, boy',
+'home parents children mom dad father mother man women boy people human'
+],[
+'👨👩👧',
+'family: man, woman, girl',
+'home parents children mom dad father mother man women girl people human'
+],[
+'👨👩👧👦',
+'family: man, woman, girl, boy',
+'home parents children mom dad father mother man women girl boy people human'
+],[
+'👨👩👦👦',
+'family: man, woman, boy, boy',
+'home parents children mom dad father mother man women boy people human'
+],[
+'👨👩👧👧',
+'family: man, woman, girl, girl',
+'home parents children mom dad father mother man women girl people human'
+],[
+'👨👨👦',
+'family: man, man, boy',
+'home parents children dad father man boy people human'
+],[
+'👨👨👧',
+'family: man, man, girl',
+'home parents children dad father man girl people human'
+],[
+'👨👨👧👦',
+'family: man, man, girl, boy',
+'home parents children dad father man girl boy people human'
+],[
+'👨👨👦👦',
+'family: man, man, boy, boy',
+'home parents children dad father man boy people human'
+],[
+'👨👨👧👧',
+'family: man, man, girl, girl',
+'home parents children dad father man girl people human'
+],[
+'👩👩👦',
+'family: woman, woman, boy',
+'home parents children mom mother women boy people human'
+],[
+'👩👩👧',
+'family: woman, woman, girl',
+'home parents children mom mother women girl people human'
+],[
+'👩👩👧👦',
+'family: woman, woman, girl, boy',
+'home parents children mom mother women girl boy people human'
+],[
+'👩👩👦👦',
+'family: woman, woman, boy, boy',
+'home parents children mom mother women boy people human'
+],[
+'👩👩👧👧',
+'family: woman, woman, girl, girl',
+'home parents children mom mother women girl people human'
+],[
+'👨👦',
+'family: man, boy',
+'home parents children dad father man boy people human'
+],[
+'👨👦👦',
+'family: man, boy, boy',
+'home parents children dad father man boy people human'
+],[
+'👨👧',
+'family: man, girl',
+'home parents children dad father man girl people human'
+],[
+'👨👧👦',
+'family: man, girl, boy',
+'home parents children dad father man girl boy people human'
+],[
+'👨👧👧',
+'family: man, girl, girl',
+'home parents children dad father man girl people human'
+],[
+'👩👦',
+'family: woman, boy',
+'home parents children mom mother women boy people human'
+],[
+'👩👦👦',
+'family: woman, boy, boy',
+'home parents children mom mother women boy people human'
+],[
+'👩👧',
+'family: woman, girl',
+'home parents children mom mother women girl people human'
+],[
+'👩👧👦',
+'family: woman, girl, boy',
+'home parents children mom mother women girl boy people human'
+],[
+'👩👧👧',
+'family: woman, girl, girl',
+'home parents children mom mother women girl people human'
+],[
+'🗣️',
+'speaking head',
+'face head silhouette speak speaking user person human sing say talk'
+],[
+'👤',
+'bust in silhouette',
+'bust silhouette user person human'
+],[
+'👥',
+'busts in silhouette',
+'bust silhouette user person human group team'
+],[
+'🫂',
+'people hugging',
+'goodbye hello hug thanks care'
+],[
+'👪',
+'family',
+'home parents children mom dad father mother man women girl boy people human'
+],[
+'🧑🧑🧒',
+'family: adult, adult, child',
+'home parents children mom dad father mother man women girl boy people human'
+],[
+'🧑🧑🧒🧒',
+'family: adult, adult, child, child',
+'home parents children mom dad father mother man women girl boy people human'
+],[
+'🧑🧒',
+'family: adult, child',
+'home parents children mom dad father mother man women girl boy people human'
+],[
+'🧑🧒🧒',
+'family: adult, child, child',
+'home parents children mom dad father mother man women girl boy people human'
+],[
+'👣',
+'footprints',
+'barefoot clothing footprint print feet tracking walking beach'
+],[
+'',
+'fingerprint',
+'clue crime detective fingerprint forensics identity mystery print safety trace'
+],[
+'🦰',
+'red hair',
+'ginger redhead'
+],[
+'🦱',
+'curly hair',
+'afro curly ringlets'
+],[
+'🦳',
+'white hair',
+'gray hair old white'
+],[
+'🦲',
+'bald',
+'chemotherapy hairless no hair shaven'
+],
+
+
+
+// Animals & Nature
+[
+'🐵',
+'monkey face',
+'face monkey animal nature circus'
+],[
+'🐒',
+'monkey',
+'animal nature banana circus'
+],[
+'🦍',
+'gorilla',
+'animal nature circus'
+],[
+'🦧',
+'orangutan',
+'ape animal'
+],[
+'🐶',
+'dog face',
+'dog face pet animal friend nature woof puppy faithful'
+],[
+'🐕',
+'dog',
+'pet animal nature friend doge faithful'
+],[
+'🦮',
+'guide dog',
+'accessibility blind guide animal'
+],[
+'🐕🦺',
+'service dog',
+'accessibility assistance dog service blind animal'
+],[
+'🐩',
+'poodle',
+'dog animal 101 nature pet'
+],[
+'🐺',
+'wolf',
+'face animal nature wild'
+],[
+'🦊',
+'fox',
+'face animal nature'
+],[
+'🦝',
+'raccoon',
+'curious sly animal nature'
+],[
+'🐱',
+'cat face',
+'cat face pet animal meow nature kitten'
+],[
+'🐈',
+'cat',
+'pet animal meow cats'
+],[
+'🐈⬛',
+'black cat',
+'black cat unlucky superstition luck'
+],[
+'🦁',
+'lion',
+'face Leo zodiac animal nature'
+],[
+'🐯',
+'tiger face',
+'face tiger animal cat danger wild nature roar'
+],[
+'🐅',
+'tiger',
+'animal nature roar'
+],[
+'🐆',
+'leopard',
+'animal nature'
+],[
+'🐴',
+'horse face',
+'face horse animal brown nature'
+],[
+'🫎',
+'moose',
+'animal antlers elk mammal moose canada sweden'
+],[
+'🫏',
+'donkey',
+'animal ass burro donkey mammal mule stubborn'
+],[
+'🐎',
+'horse',
+'equestrian racehorse racing animal gamble luck'
+],[
+'🦄',
+'unicorn',
+'face animal nature mystical'
+],[
+'🦓',
+'zebra',
+'stripe animal nature stripes safari'
+],[
+'🦌',
+'deer',
+'animal nature horns venison'
+],[
+'🦬',
+'bison',
+'buffalo herd wisent ox'
+],[
+'🐮',
+'cow face',
+'cow face beef ox animal nature moo milk'
+],[
+'🐂',
+'ox',
+'bull Taurus zodiac animal cow beef'
+],[
+'🐃',
+'water buffalo',
+'buffalo water animal nature ox cow'
+],[
+'🐄',
+'cow',
+'beef ox animal nature moo milk'
+],[
+'🐷',
+'pig face',
+'face pig animal oink nature'
+],[
+'🐖',
+'pig',
+'sow animal nature'
+],[
+'🐗',
+'boar',
+'pig animal nature'
+],[
+'🐽',
+'pig nose',
+'face nose pig animal oink'
+],[
+'🐏',
+'ram',
+'Aries male sheep zodiac animal nature'
+],[
+'🐑',
+'ewe',
+'female sheep animal nature wool shipit'
+],[
+'🐐',
+'goat',
+'Capricorn zodiac animal nature'
+],[
+'🐪',
+'camel',
+'dromedary hump animal hot desert'
+],[
+'🐫',
+'two-hump camel',
+'bactrian camel hump two hump camel animal nature hot desert'
+],[
+'🦙',
+'llama',
+'alpaca guanaco vicuña wool animal nature'
+],[
+'🦒',
+'giraffe',
+'spots animal nature safari'
+],[
+'🐘',
+'elephant',
+'animal nature nose th circus'
+],[
+'🦣',
+'mammoth',
+'extinction large tusk woolly elephant tusks'
+],[
+'🦏',
+'rhinoceros',
+'animal nature horn'
+],[
+'🦛',
+'hippopotamus',
+'hippo animal nature'
+],[
+'🐭',
+'mouse face',
+'face mouse animal nature cheese wedge rodent'
+],[
+'🐁',
+'mouse',
+'animal nature rodent'
+],[
+'🐀',
+'rat',
+'animal mouse rodent'
+],[
+'🐹',
+'hamster',
+'face pet animal nature'
+],[
+'🐰',
+'rabbit face',
+'bunny face pet rabbit animal nature spring magic'
+],[
+'🐇',
+'rabbit',
+'bunny pet animal nature magic spring'
+],[
+'🐿️',
+'chipmunk',
+'squirrel animal nature rodent'
+],[
+'🦫',
+'beaver',
+'dam animal rodent'
+],[
+'🦔',
+'hedgehog',
+'spiny animal nature'
+],[
+'🦇',
+'bat',
+'vampire animal nature blind'
+],[
+'🐻',
+'bear',
+'face animal nature wild'
+],[
+'🐻❄️',
+'polar bear',
+'arctic bear white animal'
+],[
+'🐨',
+'koala',
+'bear animal nature'
+],[
+'🐼',
+'panda',
+'face animal nature'
+],[
+'🦥',
+'sloth',
+'lazy slow animal'
+],[
+'🦦',
+'otter',
+'fishing playful animal'
+],[
+'🦨',
+'skunk',
+'stink animal'
+],[
+'🦘',
+'kangaroo',
+'Australia joey jump marsupial animal nature australia hop'
+],[
+'🦡',
+'badger',
+'honey badger pester animal nature honey'
+],[
+'🐾',
+'paw prints',
+'feet paw print animal tracking footprints dog cat pet'
+],[
+'🦃',
+'turkey',
+'bird animal'
+],[
+'🐔',
+'chicken',
+'bird animal cluck nature'
+],[
+'🐓',
+'rooster',
+'bird animal nature chicken'
+],[
+'🐣',
+'hatching chick',
+'baby bird chick hatching animal chicken egg born'
+],[
+'🐤',
+'baby chick',
+'baby bird chick animal chicken'
+],[
+'🐥',
+'front-facing baby chick',
+'baby bird chick front facing baby chick animal chicken'
+],[
+'🐦',
+'bird',
+'animal nature fly tweet spring'
+],[
+'🐧',
+'penguin',
+'bird animal nature'
+],[
+'🕊️',
+'dove',
+'bird fly peace animal'
+],[
+'🦅',
+'eagle',
+'bird animal nature'
+],[
+'🦆',
+'duck',
+'bird animal nature mallard'
+],[
+'🦢',
+'swan',
+'bird cygnet ugly duckling animal nature'
+],[
+'🦉',
+'owl',
+'bird wise animal nature hoot'
+],[
+'🦤',
+'dodo',
+'extinction large Mauritius animal bird'
+],[
+'🪶',
+'feather',
+'bird flight light plumage fly'
+],[
+'🦩',
+'flamingo',
+'flamboyant tropical animal'
+],[
+'🦚',
+'peacock',
+'bird ostentatious peahen proud animal nature'
+],[
+'🦜',
+'parrot',
+'bird pirate talk animal nature'
+],[
+'🪽',
+'wing',
+'angelic aviation bird flying mythology wing'
+],[
+'🐦⬛',
+'black bird',
+'bird black crow raven rook'
+],[
+'🪿',
+'goose',
+'bird fowl wild goose honk silly'
+],[
+'🐦🔥',
+'phoenix',
+'fantasy firebird phoenix rebirth reincarnation'
+],[
+'🐸',
+'frog',
+'face animal nature croak toad'
+],[
+'🐊',
+'crocodile',
+'animal nature reptile lizard alligator'
+],[
+'🐢',
+'turtle',
+'terrapin tortoise animal slow nature'
+],[
+'🦎',
+'lizard',
+'reptile animal nature'
+],[
+'🐍',
+'snake',
+'bearer Ophiuchus serpent zodiac animal evil nature hiss python'
+],[
+'🐲',
+'dragon face',
+'dragon face fairy tale animal myth nature chinese green'
+],[
+'🐉',
+'dragon',
+'fairy tale animal myth nature chinese green'
+],[
+'🦕',
+'sauropod',
+'brachiosaurus brontosaurus diplodocus animal nature dinosaur extinct'
+],[
+'🦖',
+'T-Rex',
+'Tyrannosaurus Rex t rex animal nature dinosaur tyrannosaurus extinct'
+],[
+'🐳',
+'spouting whale',
+'face spouting whale animal nature sea ocean'
+],[
+'🐋',
+'whale',
+'animal nature sea ocean'
+],[
+'🐬',
+'dolphin',
+'flipper animal nature fish sea ocean fins beach'
+],[
+'🦭',
+'seal',
+'sea Lion animal creature sea'
+],[
+'🐟',
+'fish',
+'Pisces zodiac animal food nature'
+],[
+'🐠',
+'tropical fish',
+'fish tropical animal swim ocean beach nemo'
+],[
+'🐡',
+'blowfish',
+'fish animal nature food sea ocean'
+],[
+'🦈',
+'shark',
+'fish animal nature sea ocean jaws fins beach'
+],[
+'🐙',
+'octopus',
+'animal creature ocean sea nature beach'
+],[
+'🐚',
+'spiral shell',
+'shell spiral nature sea beach'
+],[
+'🪸',
+'coral',
+'ocean sea reef'
+],[
+'🪼',
+'jellyfish',
+'burn invertebrate jellyfish marine ouch stinger tentacles'
+],[
+'🐌',
+'snail',
+'slow animal shell'
+],[
+'🦋',
+'butterfly',
+'insect pretty animal nature caterpillar'
+],[
+'🐛',
+'bug',
+'insect animal nature worm'
+],[
+'🐜',
+'ant',
+'insect animal nature bug'
+],[
+'🐝',
+'honeybee',
+'bee insect animal nature bug spring honey'
+],[
+'🪲',
+'beetle',
+'bug insect'
+],[
+'🐞',
+'lady beetle',
+'beetle insect ladybird ladybug animal nature'
+],[
+'🦗',
+'cricket',
+'grasshopper Orthoptera animal chirp'
+],[
+'🪳',
+'cockroach',
+'insect pest roach pests'
+],[
+'🕷️',
+'spider',
+'insect animal arachnid'
+],[
+'🕸️',
+'spider web',
+'spider web animal insect arachnid silk'
+],[
+'🦂',
+'scorpion',
+'scorpio Scorpio zodiac animal arachnid'
+],[
+'🦟',
+'mosquito',
+'disease fever malaria pest virus animal nature insect'
+],[
+'🪰',
+'fly',
+'disease maggot pest rotting insect'
+],[
+'🪱',
+'worm',
+'annelid earthworm parasite animal'
+],[
+'🦠',
+'microbe',
+'amoeba bacteria virus germs'
+],[
+'💐',
+'bouquet',
+'flower flowers nature spring'
+],[
+'🌸',
+'cherry blossom',
+'blossom cherry flower nature plant spring'
+],[
+'💮',
+'white flower',
+'flower japanese spring'
+],[
+'🪷',
+'lotus',
+'Buddhism flower Hinduism India purity Vietnam'
+],[
+'🏵️',
+'rosette',
+'plant flower decoration military'
+],[
+'🌹',
+'rose',
+'flower flowers valentines love spring'
+],[
+'🥀',
+'wilted flower',
+'flower wilted plant nature'
+],[
+'🌺',
+'hibiscus',
+'flower plant vegetable flowers beach'
+],[
+'🌻',
+'sunflower',
+'flower sun nature plant fall'
+],[
+'🌼',
+'blossom',
+'blossom flower nature flowers yellow'
+],[
+'🌷',
+'tulip',
+'flower flowers plant nature summer spring blossom'
+],[
+'🪻',
+'hyacinth',
+'bluebonnet flower hyacinth lavender lupine snapdragon blossom springtime'
+],[
+'🌱',
+'seedling',
+'young plant nature grass lawn spring'
+],[
+'🪴',
+'potted plant',
+'boring grow house nurturing plant useless greenery'
+],[
+'🌲',
+'evergreen tree',
+'tree plant nature'
+],[
+'🌳',
+'deciduous tree',
+'deciduous shedding tree plant nature'
+],[
+'🌴',
+'palm tree',
+'palm tree plant vegetable nature summer beach mojito tropical'
+],[
+'🌵',
+'cactus',
+'plant vegetable nature'
+],[
+'🌾',
+'sheaf of rice',
+'ear grain rice nature plant'
+],[
+'🌿',
+'herb',
+'leaf vegetable plant medicine weed grass lawn'
+],[
+'☘️',
+'shamrock',
+'plant vegetable nature irish clover'
+],[
+'🍀',
+'four leaf clover',
+'4 clover four four-leaf clover leaf vegetable plant nature lucky irish'
+],[
+'🍁',
+'maple leaf',
+'falling leaf maple nature plant vegetable ca fall'
+],[
+'🍂',
+'fallen leaf',
+'falling leaf nature plant vegetable leaves'
+],[
+'🍃',
+'leaf fluttering in wind',
+'blow flutter leaf wind nature plant tree vegetable grass lawn spring'
+],[
+'🪹',
+'empty nest',
+'nesting birds'
+],[
+'🪺',
+'nest with eggs',
+'nesting birds'
+],[
+'🍄',
+'mushroom',
+'mushroom toadstool fungus'
+],[
+'',
+'leafless tree',
+'bare barren branches dead drought leafless tree trunk winter wood'
+],
+
+
+
+// Food & Drink
+[
+'🍇',
+'grapes',
+'fruit grape food wine'
+],[
+'🍈',
+'melon',
+'fruit nature food'
+],[
+'🍉',
+'watermelon',
+'fruit food picnic summer'
+],[
+'🍊',
+'tangerine',
+'fruit orange food nature'
+],[
+'🍋',
+'lemon',
+'citrus fruit nature'
+],[
+'🍋🟩',
+'lime',
+'citrus fruit lime tropical'
+],[
+'🍌',
+'banana',
+'fruit food monkey'
+],[
+'🍍',
+'pineapple',
+'fruit nature food'
+],[
+'🥭',
+'mango',
+'fruit tropical food'
+],[
+'🍎',
+'red apple',
+'apple fruit red mac school'
+],[
+'🍏',
+'green apple',
+'apple fruit green nature'
+],[
+'🍐',
+'pear',
+'fruit nature food'
+],[
+'🍑',
+'peach',
+'fruit nature food'
+],[
+'🍒',
+'cherries',
+'berries cherry fruit red food'
+],[
+'🍓',
+'strawberry',
+'berry fruit food nature'
+],[
+'🫐',
+'blueberries',
+'berry bilberry blue blueberry fruit'
+],[
+'🥝',
+'kiwi fruit',
+'food fruit kiwi'
+],[
+'🍅',
+'tomato',
+'fruit vegetable nature food'
+],[
+'🫒',
+'olive',
+'food fruit'
+],[
+'🥥',
+'coconut',
+'palm piña colada fruit nature food'
+],[
+'🥑',
+'avocado',
+'food fruit'
+],[
+'🍆',
+'eggplant',
+'aubergine vegetable nature food'
+],[
+'🥔',
+'potato',
+'food vegetable tuber vegatable starch'
+],[
+'🥕',
+'carrot',
+'food vegetable orange'
+],[
+'🌽',
+'ear of corn',
+'corn ear maize maze food vegetable plant'
+],[
+'🌶️',
+'hot pepper',
+'hot pepper food spicy chilli chili'
+],[
+'🫑',
+'bell pepper',
+'capsicum pepper vegetable fruit plant'
+],[
+'🥒',
+'cucumber',
+'food pickle vegetable fruit'
+],[
+'🥬',
+'leafy green',
+'bok choy cabbage kale lettuce food vegetable plant'
+],[
+'🥦',
+'broccoli',
+'wild cabbage fruit food vegetable'
+],[
+'🧄',
+'garlic',
+'flavoring food spice cook'
+],[
+'🧅',
+'onion',
+'flavoring cook food spice'
+],[
+'🥜',
+'peanuts',
+'food nut peanut vegetable'
+],[
+'🫘',
+'beans',
+'food kidney legume'
+],[
+'🌰',
+'chestnut',
+'plant food squirrel'
+],[
+'🫚',
+'ginger root',
+'beer ginger root spice flavour cooking'
+],[
+'🫛',
+'pea pod',
+'beans edamame legume pea pod vegetable green'
+],[
+'🍄🟫',
+'brown mushroom',
+'brown food fungi fungus mushroom nature pizza portobello shiitake shroom spore sprout toppings truffle vegetable vegetarian veggie'
+],[
+'',
+'root vegetable',
+'beet food garden radish root salad turnip vegetable vegetarian'
+],[
+'🍞',
+'bread',
+'loaf food wheat breakfast toast'
+],[
+'🥐',
+'croissant',
+'bread breakfast food french roll'
+],[
+'🥖',
+'baguette bread',
+'baguette bread food french'
+],[
+'🫓',
+'flatbread',
+'arepa lavash naan pita flour food'
+],[
+'🥨',
+'pretzel',
+'twisted convoluted food bread'
+],[
+'🥯',
+'bagel',
+'bakery breakfast schmear food bread'
+],[
+'🥞',
+'pancakes',
+'breakfast crêpe food hotcake pancake flapjacks hotcakes'
+],[
+'🧇',
+'waffle',
+'breakfast indecisive iron food'
+],[
+'🧀',
+'cheese wedge',
+'cheese food chadder'
+],[
+'🍖',
+'meat on bone',
+'bone meat good food drumstick'
+],[
+'🍗',
+'poultry leg',
+'bone chicken drumstick leg poultry food meat bird turkey'
+],[
+'🥩',
+'cut of meat',
+'chop lambchop porkchop steak food cow meat cut'
+],[
+'🥓',
+'bacon',
+'breakfast food meat pork pig'
+],[
+'🍔',
+'hamburger',
+'burger meat fast food beef cheeseburger mcdonalds burger king'
+],[
+'🍟',
+'french fries',
+'french fries chips snack fast food'
+],[
+'🍕',
+'pizza',
+'cheese slice food party'
+],[
+'🌭',
+'hot dog',
+'frankfurter hotdog sausage food'
+],[
+'🥪',
+'sandwich',
+'bread food lunch'
+],[
+'🌮',
+'taco',
+'mexican food'
+],[
+'🌯',
+'burrito',
+'mexican wrap food'
+],[
+'🫔',
+'tamale',
+'mexican wrapped food masa'
+],[
+'🥙',
+'stuffed flatbread',
+'falafel flatbread food gyro kebab stuffed'
+],[
+'🧆',
+'falafel',
+'chickpea meatball food'
+],[
+'🥚',
+'egg',
+'breakfast food chicken'
+],[
+'🍳',
+'cooking',
+'breakfast egg frying pan food kitchen'
+],[
+'🥘',
+'shallow pan of food',
+'casserole food paella pan shallow cooking'
+],[
+'🍲',
+'pot of food',
+'pot stew food meat soup'
+],[
+'🫕',
+'fondue',
+'cheese chocolate melted pot Swiss food'
+],[
+'🥣',
+'bowl with spoon',
+'breakfast cereal congee oatmeal porridge food'
+],[
+'🥗',
+'green salad',
+'food green salad healthy lettuce'
+],[
+'🍿',
+'popcorn',
+'food movie theater films snack'
+],[
+'🧈',
+'butter',
+'dairy food cook'
+],[
+'🧂',
+'salt',
+'condiment shaker'
+],[
+'🥫',
+'canned food',
+'can food soup'
+],[
+'🍱',
+'bento box',
+'bento box food japanese'
+],[
+'🍘',
+'rice cracker',
+'cracker rice food japanese'
+],[
+'🍙',
+'rice ball',
+'ball Japanese rice food japanese'
+],[
+'🍚',
+'cooked rice',
+'cooked rice food china asian'
+],[
+'🍛',
+'curry rice',
+'curry rice food spicy hot indian'
+],[
+'🍜',
+'steaming bowl',
+'bowl noodle ramen steaming food japanese chopsticks'
+],[
+'🍝',
+'spaghetti',
+'pasta food italian noodle'
+],[
+'🍠',
+'roasted sweet potato',
+'potato roasted sweet food nature'
+],[
+'🍢',
+'oden',
+'kebab seafood skewer stick food japanese'
+],[
+'🍣',
+'sushi',
+'food fish japanese rice'
+],[
+'🍤',
+'fried shrimp',
+'fried prawn shrimp tempura food animal appetizer summer'
+],[
+'🍥',
+'fish cake with swirl',
+'cake fish pastry swirl food japan sea beach narutomaki pink kamaboko surimi ramen'
+],[
+'🥮',
+'moon cake',
+'autumn festival yuèbǐng food'
+],[
+'🍡',
+'dango',
+'dessert Japanese skewer stick sweet food japanese barbecue meat'
+],[
+'🥟',
+'dumpling',
+'empanada gyōza jiaozi pierogi potsticker food'
+],[
+'🥠',
+'fortune cookie',
+'prophecy food'
+],[
+'🥡',
+'takeout box',
+'oyster pail food leftovers'
+],[
+'🦀',
+'crab',
+'Cancer zodiac animal crustacean'
+],[
+'🦞',
+'lobster',
+'bisque claws seafood animal nature'
+],[
+'🦐',
+'shrimp',
+'food shellfish small animal ocean nature seafood'
+],[
+'🦑',
+'squid',
+'food molusc animal nature ocean sea'
+],[
+'🦪',
+'oyster',
+'diving pearl food'
+],[
+'🍦',
+'soft ice cream',
+'cream dessert ice icecream soft sweet food hot summer'
+],[
+'🍧',
+'shaved ice',
+'dessert ice shaved sweet hot summer'
+],[
+'🍨',
+'ice cream',
+'cream dessert ice sweet food hot'
+],[
+'🍩',
+'doughnut',
+'breakfast dessert donut sweet food snack'
+],[
+'🍪',
+'cookie',
+'dessert sweet food snack oreo chocolate'
+],[
+'🎂',
+'birthday cake',
+'birthday cake celebration dessert pastry sweet food'
+],[
+'🍰',
+'shortcake',
+'cake dessert pastry slice sweet food'
+],[
+'🧁',
+'cupcake',
+'bakery sweet food dessert'
+],[
+'🥧',
+'pie',
+'filling pastry fruit meat food dessert'
+],[
+'🍫',
+'chocolate bar',
+'bar chocolate dessert sweet food snack'
+],[
+'🍬',
+'candy',
+'dessert sweet snack lolly'
+],[
+'🍭',
+'lollipop',
+'candy dessert sweet food snack'
+],[
+'🍮',
+'custard',
+'dessert pudding sweet food'
+],[
+'🍯',
+'honey pot',
+'honey honeypot pot sweet bees kitchen'
+],[
+'🍼',
+'baby bottle',
+'baby bottle drink milk food container'
+],[
+'🥛',
+'glass of milk',
+'drink glass milk beverage cow'
+],[
+'☕',
+'hot beverage',
+'beverage coffee drink hot steaming tea caffeine latte espresso'
+],[
+'🫖',
+'teapot',
+'drink pot tea hot'
+],[
+'🍵',
+'teacup without handle',
+'beverage cup drink tea teacup bowl breakfast green british'
+],[
+'🍶',
+'sake',
+'bar beverage bottle cup drink wine drunk japanese alcohol booze'
+],[
+'🍾',
+'bottle with popping cork',
+'bar bottle cork drink popping wine celebration'
+],[
+'🍷',
+'wine glass',
+'bar beverage drink glass wine drunk alcohol booze'
+],[
+'🍸',
+'cocktail glass',
+'bar cocktail drink glass drunk alcohol beverage booze mojito'
+],[
+'🍹',
+'tropical drink',
+'bar drink tropical beverage cocktail summer beach alcohol booze mojito'
+],[
+'🍺',
+'beer mug',
+'bar beer drink mug relax beverage drunk party pub summer alcohol booze'
+],[
+'🍻',
+'clinking beer mugs',
+'bar beer clink drink mug relax beverage drunk party pub summer alcohol booze'
+],[
+'🥂',
+'clinking glasses',
+'celebrate clink drink glass beverage party alcohol cheers wine champagne toast'
+],[
+'🥃',
+'tumbler glass',
+'glass liquor shot tumbler whisky drink beverage drunk alcohol booze bourbon scotch'
+],[
+'🫗',
+'pouring liquid',
+'drink empty glass spill'
+],[
+'🥤',
+'cup with straw',
+'juice soda malt soft drink water drink'
+],[
+'🧋',
+'bubble tea',
+'bubble milk pearl tea taiwan boba milk tea straw'
+],[
+'🧃',
+'beverage box',
+'beverage box juice straw sweet drink'
+],[
+'🧉',
+'mate',
+'drink tea beverage'
+],[
+'🧊',
+'ice',
+'cold ice cube iceberg water'
+],[
+'🥢',
+'chopsticks',
+'hashi jeotgarak kuaizi food'
+],[
+'🍽️',
+'fork and knife with plate',
+'cooking fork knife plate food eat meal lunch dinner restaurant'
+],[
+'🍴',
+'fork and knife',
+'cooking cutlery fork knife kitchen'
+],[
+'🥄',
+'spoon',
+'tableware cutlery kitchen'
+],[
+'🔪',
+'kitchen knife',
+'cooking hocho knife tool weapon blade cutlery kitchen'
+],[
+'🫙',
+'jar',
+'condiment container empty sauce store'
+],[
+'🏺',
+'amphora',
+'Aquarius cooking drink jug zodiac vase jar'
+],
+
+
+
+// Travel & Places
+[
+'🌍',
+'globe showing Europe-Africa',
+'Africa earth Europe globe world globe showing europe africa international'
+],[
+'🌎',
+'globe showing Americas',
+'Americas earth globe world globe showing americas USA international'
+],[
+'🌏',
+'globe showing Asia-Australia',
+'Asia Australia earth globe world globe showing asia australia east international'
+],[
+'🌐',
+'globe with meridians',
+'earth globe meridians world international internet interweb i18n'
+],[
+'🗺️',
+'world map',
+'map world location direction'
+],[
+'🗾',
+'map of Japan',
+'Japan map map of japan nation country japanese asia'
+],[
+'🧭',
+'compass',
+'magnetic navigation orienteering'
+],[
+'🏔️',
+'snow-capped mountain',
+'cold mountain snow snow capped mountain photo nature environment winter'
+],[
+'⛰️',
+'mountain',
+'photo nature environment'
+],[
+'🌋',
+'volcano',
+'eruption mountain photo nature disaster'
+],[
+'🗻',
+'mount fuji',
+'fuji mountain photo nature japanese'
+],[
+'🏕️',
+'camping',
+'photo outdoors tent'
+],[
+'☂️',
+'beach with umbrella',
+'beach umbrella weather summer sunny sand mojito'
+],[
+'🏜️',
+'desert',
+'photo warm saharah'
+],[
+'🏝️',
+'desert island',
+'desert island photo tropical mojito'
+],[
+'🏞️',
+'national park',
+'park photo environment nature'
+],[
+'🏟️',
+'stadium',
+'photo place sports concert venue'
+],[
+'🏛️',
+'classical building',
+'classical art culture history'
+],[
+'🏗️',
+'building construction',
+'construction wip working progress'
+],[
+'🧱',
+'brick',
+'bricks clay mortar wall'
+],[
+'🪨',
+'rock',
+'boulder heavy solid stone'
+],[
+'🪵',
+'wood',
+'log lumber timber nature trunk'
+],[
+'🛖',
+'hut',
+'house roundhouse yurt structure'
+],[
+'🏘️',
+'houses',
+'buildings photo'
+],[
+'🏚️',
+'derelict house',
+'derelict house abandon evict broken building'
+],[
+'🏠',
+'house',
+'home building'
+],[
+'🏡',
+'house with garden',
+'garden home house plant nature'
+],[
+'🏢',
+'office building',
+'building bureau work'
+],[
+'🏣',
+'Japanese post office',
+'Japanese post japanese post office building envelope communication'
+],[
+'🏤',
+'post office',
+'European post building email'
+],[
+'🏥',
+'hospital',
+'doctor medicine building health surgery'
+],[
+'🏦',
+'bank',
+'building money sales cash business enterprise'
+],[
+'🏨',
+'hotel',
+'building accomodation checkin'
+],[
+'🏩',
+'love hotel',
+'hotel love like affection dating'
+],[
+'🏪',
+'convenience store',
+'convenience store building shopping groceries'
+],[
+'🏫',
+'school',
+'building student education learn teach'
+],[
+'🏬',
+'department store',
+'department store building shopping mall'
+],[
+'🏭',
+'factory',
+'building industry pollution smoke'
+],[
+'🏯',
+'Japanese castle',
+'castle Japanese japanese castle photo building'
+],[
+'🏰',
+'castle',
+'European building royalty history'
+],[
+'💒',
+'wedding',
+'chapel romance love like affection couple marriage bride groom'
+],[
+'🗼',
+'Tokyo tower',
+'Tokyo tower tokyo tower photo japanese'
+],[
+'🗽',
+'Statue of Liberty',
+'liberty statue statue of liberty american newyork'
+],[
+'⛪',
+'church',
+'Christian cross religion building christ'
+],[
+'🕌',
+'mosque',
+'islam Muslim religion worship minaret'
+],[
+'🛕',
+'hindu temple',
+'hindu temple religion'
+],[
+'🕍',
+'synagogue',
+'Jew Jewish religion temple judaism worship jewish'
+],[
+'⛩️',
+'shinto shrine',
+'religion shinto shrine temple japan kyoto'
+],[
+'🕋',
+'kaaba',
+'islam Muslim religion mecca mosque'
+],[
+'⛲',
+'fountain',
+'photo summer water fresh'
+],[
+'⛺',
+'tent',
+'camping photo outdoors'
+],[
+'🌁',
+'foggy',
+'fog photo mountain'
+],[
+'🌃',
+'night with stars',
+'night star evening city downtown'
+],[
+'🏙️',
+'cityscape',
+'city photo night life urban'
+],[
+'🌄',
+'sunrise over mountains',
+'morning mountain sun sunrise view vacation photo'
+],[
+'🌅',
+'sunrise',
+'morning sun view vacation photo'
+],[
+'🌆',
+'cityscape at dusk',
+'city dusk evening landscape sunset photo sky buildings'
+],[
+'🌇',
+'sunset',
+'dusk sun photo good morning dawn'
+],[
+'🌉',
+'bridge at night',
+'bridge night photo sanfrancisco'
+],[
+'♨️',
+'hot springs',
+'hot hotsprings springs steaming bath warm relax'
+],[
+'🎠',
+'carousel horse',
+'carousel horse photo carnival'
+],[
+'🛝',
+'playground slide',
+'amusement park play'
+],[
+'🎡',
+'ferris wheel',
+'amusement park ferris wheel photo carnival londoneye'
+],[
+'🎢',
+'roller coaster',
+'amusement park coaster roller carnival playground photo fun'
+],[
+'💈',
+'barber pole',
+'barber haircut pole hair salon style'
+],[
+'🎪',
+'circus tent',
+'circus tent festival carnival party'
+],[
+'🚂',
+'locomotive',
+'engine railway steam train transportation vehicle'
+],[
+'🚃',
+'railway car',
+'car electric railway train tram trolleybus transportation vehicle'
+],[
+'🚄',
+'high-speed train',
+'railway shinkansen speed train high speed train transportation vehicle'
+],[
+'🚅',
+'bullet train',
+'bullet railway shinkansen speed train transportation vehicle fast public travel'
+],[
+'🚆',
+'train',
+'railway transportation vehicle'
+],[
+'🚇',
+'metro',
+'subway transportation blue-square mrt underground tube'
+],[
+'🚈',
+'light rail',
+'railway transportation vehicle'
+],[
+'🚉',
+'station',
+'railway train transportation vehicle public'
+],[
+'🚊',
+'tram',
+'trolleybus transportation vehicle'
+],[
+'🚝',
+'monorail',
+'vehicle transportation'
+],[
+'🚞',
+'mountain railway',
+'car mountain railway transportation vehicle'
+],[
+'🚋',
+'tram car',
+'car tram trolleybus transportation vehicle carriage public travel'
+],[
+'🚌',
+'bus',
+'vehicle car transportation'
+],[
+'🚍',
+'oncoming bus',
+'bus oncoming vehicle transportation'
+],[
+'🚎',
+'trolleybus',
+'bus tram trolley bart transportation vehicle'
+],[
+'🚐',
+'minibus',
+'bus vehicle car transportation'
+],[
+'🚑',
+'ambulance',
+'vehicle health 911 hospital'
+],[
+'🚒',
+'fire engine',
+'engine fire truck transportation cars vehicle'
+],[
+'🚓',
+'police car',
+'car patrol police vehicle cars transportation law legal enforcement'
+],[
+'🚔',
+'oncoming police car',
+'car oncoming police vehicle law legal enforcement 911'
+],[
+'🚕',
+'taxi',
+'vehicle uber cars transportation'
+],[
+'🚖',
+'oncoming taxi',
+'oncoming taxi vehicle cars uber'
+],[
+'🚗',
+'automobile',
+'car red transportation vehicle'
+],[
+'🚘',
+'oncoming automobile',
+'automobile car oncoming vehicle transportation'
+],[
+'🚙',
+'sport utility vehicle',
+'recreational sport utility transportation vehicle'
+],[
+'🛻',
+'pickup truck',
+'pick-up pickup truck car transportation'
+],[
+'🚚',
+'delivery truck',
+'delivery truck cars transportation'
+],[
+'🚛',
+'articulated lorry',
+'lorry semi truck vehicle cars transportation express'
+],[
+'🚜',
+'tractor',
+'vehicle car farming agriculture'
+],[
+'🏎️',
+'racing car',
+'car racing sports race fast formula f1'
+],[
+'🏍️',
+'motorcycle',
+'racing race sports fast'
+],[
+'🛵',
+'motor scooter',
+'motor scooter vehicle vespa sasha'
+],[
+'🦽',
+'manual wheelchair',
+'accessibility'
+],[
+'🦼',
+'motorized wheelchair',
+'accessibility'
+],[
+'🛺',
+'auto rickshaw',
+'tuk tuk move transportation'
+],[
+'🚲',
+'bicycle',
+'bike sports exercise hipster'
+],[
+'🛴',
+'kick scooter',
+'kick scooter vehicle razor'
+],[
+'🛹',
+'skateboard',
+'board'
+],[
+'🛼',
+'roller skate',
+'roller skate footwear sports'
+],[
+'🚏',
+'bus stop',
+'bus busstop stop transportation wait'
+],[
+'🛣️',
+'motorway',
+'highway road cupertino interstate'
+],[
+'🛤️',
+'railway track',
+'railway train transportation'
+],[
+'🛢️',
+'oil drum',
+'drum oil barrell'
+],[
+'⛽',
+'fuel pump',
+'diesel fuel fuelpump gas pump station gas station petroleum'
+],[
+'🛞',
+'wheel',
+'circle tire turn'
+],[
+'🚨',
+'police car light',
+'beacon car light police revolving ambulance 911 emergency alert error pinged law legal'
+],[
+'🚥',
+'horizontal traffic light',
+'light signal traffic transportation'
+],[
+'🚦',
+'vertical traffic light',
+'light signal traffic transportation driving'
+],[
+'🛑',
+'stop sign',
+'octagonal sign stop'
+],[
+'🚧',
+'construction',
+'barrier wip progress caution warning'
+],[
+'⚓',
+'anchor',
+'ship tool ferry sea boat'
+],[
+'🛟',
+'ring buoy',
+'float life preserver saver rescue safety'
+],[
+'⛵',
+'sailboat',
+'boat resort sea yacht ship summer transportation water sailing'
+],[
+'🛶',
+'canoe',
+'boat paddle water ship'
+],[
+'🚤',
+'speedboat',
+'boat ship transportation vehicle summer'
+],[
+'🛳️',
+'passenger ship',
+'passenger ship yacht cruise ferry'
+],[
+'⛴️',
+'ferry',
+'boat passenger ship yacht'
+],[
+'🛥️',
+'motor boat',
+'boat motorboat ship'
+],[
+'🚢',
+'ship',
+'boat passenger transportation titanic deploy'
+],[
+'✈️',
+'airplane',
+'aeroplane vehicle transportation flight fly'
+],[
+'🛩️',
+'small airplane',
+'aeroplane airplane flight transportation fly vehicle'
+],[
+'🛫',
+'airplane departure',
+'aeroplane airplane check-in departure departures airport flight landing'
+],[
+'🛬',
+'airplane arrival',
+'aeroplane airplane arrivals arriving landing airport flight boarding'
+],[
+'🪂',
+'parachute',
+'hang-glide parasail skydive fly glide'
+],[
+'💺',
+'seat',
+'chair sit airplane transport bus flight fly'
+],[
+'🚁',
+'helicopter',
+'vehicle transportation fly'
+],[
+'🚟',
+'suspension railway',
+'railway suspension vehicle transportation'
+],[
+'🚠',
+'mountain cableway',
+'cable gondola mountain transportation vehicle ski'
+],[
+'🚡',
+'aerial tramway',
+'aerial cable car gondola tramway transportation vehicle ski'
+],[
+'🛰️',
+'satellite',
+'space communication gps orbit spaceflight NASA ISS'
+],[
+'🚀',
+'rocket',
+'space launch ship staffmode NASA outer space fly'
+],[
+'🛸',
+'flying saucer',
+'UFO transportation vehicle ufo'
+],[
+'🛎️',
+'bellhop bell',
+'bell bellhop hotel service'
+],[
+'🧳',
+'luggage',
+'packing travel'
+],[
+'⌛',
+'hourglass done',
+'sand timer time clock oldschool limit exam quiz test'
+],[
+'⏳',
+'hourglass not done',
+'hourglass sand timer oldschool time countdown'
+],[
+'⌚',
+'watch',
+'clock time accessories'
+],[
+'⏰',
+'alarm clock',
+'alarm clock time wake'
+],[
+'⏱️',
+'stopwatch',
+'clock time deadline'
+],[
+'⏲️',
+'timer clock',
+'clock timer alarm'
+],[
+'🕰️',
+'mantelpiece clock',
+'clock time'
+],[
+'🕛',
+'twelve o’clock',
+'00 12 12:00 clock o’clock twelve twelve o clock time noon midnight midday late early schedule'
+],[
+'🕧',
+'twelve-thirty',
+'12 12:30 clock thirty twelve twelve thirty time late early schedule'
+],[
+'🕐',
+'one o’clock',
+'00 1 1:00 clock o’clock one one o clock time late early schedule'
+],[
+'🕜',
+'one-thirty',
+'1 1:30 clock one thirty one thirty time late early schedule'
+],[
+'🕑',
+'two o’clock',
+'00 2 2:00 clock o’clock two two o clock time late early schedule'
+],[
+'🕝',
+'two-thirty',
+'2 2:30 clock thirty two two thirty time late early schedule'
+],[
+'🕒',
+'three o’clock',
+'00 3 3:00 clock o’clock three three o clock time late early schedule'
+],[
+'🕞',
+'three-thirty',
+'3 3:30 clock thirty three three thirty time late early schedule'
+],[
+'🕓',
+'four o’clock',
+'00 4 4:00 clock four o’clock four o clock time late early schedule'
+],[
+'🕟',
+'four-thirty',
+'4 4:30 clock four thirty four thirty time late early schedule'
+],[
+'🕔',
+'five o’clock',
+'00 5 5:00 clock five o’clock five o clock time late early schedule'
+],[
+'🕠',
+'five-thirty',
+'5 5:30 clock five thirty five thirty time late early schedule'
+],[
+'🕕',
+'six o’clock',
+'00 6 6:00 clock o’clock six six o clock time late early schedule dawn dusk'
+],[
+'🕡',
+'six-thirty',
+'6 6:30 clock six thirty six thirty time late early schedule'
+],[
+'🕖',
+'seven o’clock',
+'00 7 7:00 clock o’clock seven seven o clock time late early schedule'
+],[
+'🕢',
+'seven-thirty',
+'7 7:30 clock seven thirty seven thirty time late early schedule'
+],[
+'🕗',
+'eight o’clock',
+'00 8 8:00 clock eight o’clock eight o clock time late early schedule'
+],[
+'🕣',
+'eight-thirty',
+'8 8:30 clock eight thirty eight thirty time late early schedule'
+],[
+'🕘',
+'nine o’clock',
+'00 9 9:00 clock nine o’clock nine o clock time late early schedule'
+],[
+'🕤',
+'nine-thirty',
+'9 9:30 clock nine thirty nine thirty time late early schedule'
+],[
+'🕙',
+'ten o’clock',
+'00 10 10:00 clock o’clock ten ten o clock time late early schedule'
+],[
+'🕥',
+'ten-thirty',
+'10 10:30 clock ten thirty ten thirty time late early schedule'
+],[
+'🕚',
+'eleven o’clock',
+'00 11 11:00 clock eleven o’clock eleven o clock time late early schedule'
+],[
+'🕦',
+'eleven-thirty',
+'11 11:30 clock eleven thirty eleven thirty time late early schedule'
+],[
+'🌑',
+'new moon',
+'dark moon nature twilight planet space night evening sleep'
+],[
+'🌒',
+'waxing crescent moon',
+'crescent moon waxing nature twilight planet space night evening sleep'
+],[
+'🌓',
+'first quarter moon',
+'moon quarter nature twilight planet space night evening sleep'
+],[
+'🌔',
+'waxing gibbous moon',
+'gibbous moon waxing nature night sky gray twilight planet space evening sleep'
+],[
+'🌕',
+'full moon',
+'full moon nature yellow twilight planet space night evening sleep'
+],[
+'🌖',
+'waning gibbous moon',
+'gibbous moon waning nature twilight planet space night evening sleep waxing gibbous moon'
+],[
+'🌗',
+'last quarter moon',
+'moon quarter nature twilight planet space night evening sleep'
+],[
+'🌘',
+'waning crescent moon',
+'crescent moon waning nature twilight planet space night evening sleep'
+],[
+'🌙',
+'crescent moon',
+'crescent moon night sleep sky evening magic'
+],[
+'🌚',
+'new moon face',
+'face moon nature twilight planet space night evening sleep'
+],[
+'🌛',
+'first quarter moon face',
+'face moon quarter nature twilight planet space night evening sleep'
+],[
+'🌜',
+'last quarter moon face',
+'face moon quarter nature twilight planet space night evening sleep'
+],[
+'🌡️',
+'thermometer',
+'weather temperature hot cold'
+],[
+'☀️',
+'sun',
+'bright rays sunny weather nature brightness summer beach spring'
+],[
+'🌝',
+'full moon face',
+'bright face full moon nature twilight planet space night evening sleep'
+],[
+'🌞',
+'sun with face',
+'bright face sun nature morning sky'
+],[
+'🪐',
+'ringed planet',
+'saturn saturnine outerspace'
+],[
+'⭐',
+'star',
+'night yellow'
+],[
+'🌟',
+'glowing star',
+'glittery glow shining sparkle star night awesome good magic'
+],[
+'🌠',
+'shooting star',
+'falling shooting star night photo'
+],[
+'🌌',
+'milky way',
+'space photo stars'
+],[
+'☁️',
+'cloud',
+'weather sky'
+],[
+'⛅',
+'sun behind cloud',
+'cloud sun weather nature cloudy morning fall spring'
+],[
+'⛈️',
+'cloud with lightning and rain',
+'cloud rain thunder weather lightning'
+],[
+'🌤️',
+'sun behind small cloud',
+'cloud sun weather'
+],[
+'🌥️',
+'sun behind large cloud',
+'cloud sun weather'
+],[
+'🌦️',
+'sun behind rain cloud',
+'cloud rain sun weather'
+],[
+'🌧️',
+'cloud with rain',
+'cloud rain weather'
+],[
+'🌨️',
+'cloud with snow',
+'cloud cold snow weather'
+],[
+'🌩️',
+'cloud with lightning',
+'cloud lightning weather thunder'
+],[
+'🌪️',
+'tornado',
+'cloud whirlwind weather cyclone twister'
+],[
+'🌫️',
+'fog',
+'cloud weather'
+],[
+'🌫️',
+'wind face',
+'blow cloud face wind gust air'
+],[
+'🌀',
+'cyclone',
+'dizzy hurricane twister typhoon weather swirl blue cloud vortex spiral whirlpool spin tornado'
+],[
+'🌈',
+'rainbow',
+'rain nature happy unicorn face photo sky spring'
+],[
+'🌂',
+'closed umbrella',
+'clothing rain umbrella weather drizzle'
+],[
+'☂️',
+'umbrella',
+'clothing rain weather spring'
+],[
+'☔',
+'umbrella with rain drops',
+'clothing drop rain umbrella rainy weather spring'
+],[
+'⛱️',
+'umbrella on ground',
+'rain sun umbrella weather summer'
+],[
+'⚡',
+'high voltage',
+'danger electric lightning voltage zap thunder weather lightning bolt fast'
+],[
+'❄️',
+'snowflake',
+'cold snow winter season weather christmas xmas'
+],[
+'☃️',
+'snowman',
+'cold snow winter season weather christmas xmas frozen'
+],[
+'⛄',
+'snowman without snow',
+'cold snow snowman winter season weather christmas xmas frozen without snow'
+],[
+'☄️',
+'comet',
+'space'
+],[
+'🔥',
+'fire',
+'flame tool hot cook'
+],[
+'💧',
+'droplet',
+'cold comic drop sweat water drip faucet spring'
+],[
+'🌊',
+'water wave',
+'ocean water wave sea nature tsunami disaster'
+],
+
+
+
+// Activities
+[
+'🎃',
+'jack-o-lantern',
+'celebration halloween jack lantern jack o lantern light pumpkin creepy fall'
+],[
+'🎄',
+'Christmas tree',
+'celebration Christmas tree christmas tree festival vacation december xmas'
+],[
+'🎆',
+'fireworks',
+'celebration photo festival carnival congratulations'
+],[
+'🎇',
+'sparkler',
+'celebration fireworks sparkle stars night shine'
+],[
+'🧨',
+'firecracker',
+'dynamite explosive fireworks boom explode explosion'
+],[
+'✨',
+'sparkles',
+'* sparkle star stars shine shiny cool awesome good magic'
+],[
+'🎈',
+'balloon',
+'celebration party birthday circus'
+],[
+'🎉',
+'party popper',
+'celebration party popper tada congratulations birthday magic circus'
+],[
+'🎊',
+'confetti ball',
+'ball celebration confetti festival party birthday circus'
+],[
+'🎋',
+'tanabata tree',
+'banner celebration Japanese tree plant nature branch summer'
+],[
+'🎍',
+'pine decoration',
+'bamboo celebration Japanese pine plant nature vegetable panda'
+],[
+'🎎',
+'Japanese dolls',
+'celebration doll festival Japanese japanese dolls japanese toy kimono'
+],[
+'🎏',
+'carp streamer',
+'carp celebration streamer fish japanese koinobori banner'
+],[
+'🎐',
+'wind chime',
+'bell celebration chime wind nature ding spring'
+],[
+'🎑',
+'moon viewing ceremony',
+'celebration ceremony moon photo japan asia tsukimi'
+],[
+'🧧',
+'red envelope',
+'gift good luck hóngbāo lai see money'
+],[
+'🎀',
+'ribbon',
+'celebration decoration pink girl bowtie'
+],[
+'🎁',
+'wrapped gift',
+'box celebration gift present wrapped birthday christmas xmas'
+],[
+'🎗️',
+'reminder ribbon',
+'celebration reminder ribbon sports cause support awareness'
+],[
+'🎟️',
+'admission tickets',
+'admission ticket sports concert entrance'
+],[
+'🎫',
+'ticket',
+'admission event concert pass'
+],[
+'🎖️',
+'military medal',
+'celebration medal military award winning army'
+],[
+'🏆',
+'trophy',
+'prize win award contest place ftw ceremony'
+],[
+'🏅',
+'sports medal',
+'medal award winning'
+],[
+'🥇',
+'1st place medal',
+'first gold medal award winning'
+],[
+'🥈',
+'2nd place medal',
+'medal second silver award'
+],[
+'🥉',
+'3rd place medal',
+'bronze medal third award'
+],[
+'⚽',
+'soccer ball',
+'ball football soccer sports'
+],[
+'⚾',
+'baseball',
+'ball sports balls'
+],[
+'🥎',
+'softball',
+'ball glove underarm sports balls'
+],[
+'🏀',
+'basketball',
+'ball hoop sports balls NBA'
+],[
+'🏐',
+'volleyball',
+'ball game sports balls'
+],[
+'🏈',
+'american football',
+'american ball football sports balls NFL'
+],[
+'🏉',
+'rugby football',
+'ball football rugby sports team'
+],[
+'🎾',
+'tennis',
+'ball racquet sports balls green'
+],[
+'🥏',
+'flying disc',
+'ultimate sports frisbee'
+],[
+'🎳',
+'bowling',
+'ball game sports fun play'
+],[
+'🏏',
+'cricket game',
+'ball bat game sports'
+],[
+'🏑',
+'field hockey',
+'ball field game hockey stick sports'
+],[
+'🏒',
+'ice hockey',
+'game hockey ice puck stick sports'
+],[
+'🥍',
+'lacrosse',
+'ball goal stick sports'
+],[
+'🏓',
+'ping pong',
+'ball bat game paddle table tennis sports pingpong'
+],[
+'🏸',
+'badminton',
+'birdie game racquet shuttlecock sports'
+],[
+'🥊',
+'boxing glove',
+'boxing glove sports fighting'
+],[
+'🥋',
+'martial arts uniform',
+'judo karate martial arts taekwondo uniform'
+],[
+'🥅',
+'goal net',
+'goal net sports'
+],[
+'⛳',
+'flag in hole',
+'golf hole sports business flag summer'
+],[
+'⛸️',
+'ice skate',
+'ice skate sports'
+],[
+'🎣',
+'fishing pole',
+'fish pole food hobby summer'
+],[
+'🤿',
+'diving mask',
+'diving scuba snorkeling sport ocean'
+],[
+'🎽',
+'running shirt',
+'athletics running sash shirt play pageant'
+],[
+'🎿',
+'skis',
+'ski snow sports winter cold'
+],[
+'🛷',
+'sled',
+'sledge sleigh luge toboggan'
+],[
+'🥌',
+'curling stone',
+'game rock sports'
+],[
+'🎯',
+'bullseye',
+'dart direct hit game hit target play bar'
+],[
+'🪀',
+'yo-yo',
+'fluctuate toy yo yo'
+],[
+'🪁',
+'kite',
+'fly soar wind'
+],[
+'🔫',
+'water pistol',
+'gun handgun pistol revolver tool water weapon violence'
+],[
+'🎱',
+'pool 8 ball',
+'8 ball billiard eight game pool hobby luck magic'
+],[
+'🔮',
+'crystal ball',
+'ball crystal fairy tale fantasy fortune tool disco party magic circus fortune teller'
+],[
+'🪄',
+'magic wand',
+'magic witch wizard supernature power'
+],[
+'🎮',
+'video game',
+'controller game play console PS4'
+],[
+'🕹️',
+'joystick',
+'game video game play'
+],[
+'🎰',
+'slot machine',
+'game slot bet gamble vegas fruit machine luck casino'
+],[
+'🎲',
+'game die',
+'dice die game random tabletop play luck'
+],[
+'🧩',
+'puzzle piece',
+'clue interlocking jigsaw piece puzzle'
+],[
+'🧸',
+'teddy bear',
+'plaything plush stuffed toy'
+],[
+'🪅',
+'piñata',
+'celebration party pinata mexico candy'
+],[
+'🪩',
+'mirror ball',
+'dance disco glitter party'
+],[
+'🪆',
+'nesting dolls',
+'doll nesting russia matryoshka toy'
+],[
+'♠️',
+'spade suit',
+'card game poker cards suits magic'
+],[
+'♥️',
+'heart suit',
+'card game poker cards magic suits'
+],[
+'♦️',
+'diamond suit',
+'card game poker cards magic suits'
+],[
+'♣️',
+'club suit',
+'card game poker cards magic suits'
+],[
+'♟️',
+'chess pawn',
+'chess dupe expendable'
+],[
+'🃏',
+'joker',
+'card game wildcard poker cards play magic'
+],[
+'🀄',
+'mahjong red dragon',
+'game mahjong red play chinese kanji'
+],[
+'🎴',
+'flower playing cards',
+'card flower game Japanese playing sunset red'
+],[
+'🎭',
+'performing arts',
+'art mask performing theater theatre acting drama'
+],[
+'🖼️',
+'framed picture',
+'art frame museum painting picture photography'
+],[
+'🎨',
+'artist palette',
+'art museum painting palette design paint draw colors'
+],[
+'🧵',
+'thread',
+'needle sewing spool string'
+],[
+'🪡',
+'sewing needle',
+'embroidery needle sewing stitches sutures tailoring'
+],[
+'🧶',
+'yarn',
+'ball crochet knit'
+],[
+'🪢',
+'knot',
+'rope tangled tie twine twist scout'
+],
+
+
+
+// Objects
+[
+'👓',
+'glasses',
+'clothing eye eyeglasses eyewear fashion accessories eyesight nerdy dork geek'
+],[
+'🕶️',
+'sunglasses',
+'dark eye eyewear glasses face cool accessories'
+],[
+'🥽',
+'goggles',
+'eye protection swimming welding eyes protection safety'
+],[
+'🥼',
+'lab coat',
+'doctor experiment scientist chemist'
+],[
+'🦺',
+'safety vest',
+'emergency safety vest protection'
+],[
+'👔',
+'necktie',
+'clothing tie shirt suitup formal fashion cloth business'
+],[
+'👕',
+'t-shirt',
+'clothing shirt tshirt t shirt fashion cloth casual tee'
+],[
+'👖',
+'jeans',
+'clothing pants trousers fashion shopping'
+],[
+'🧣',
+'scarf',
+'neck winter clothes'
+],[
+'🧤',
+'gloves',
+'hand hands winter clothes'
+],[
+'🧥',
+'coat',
+'jacket'
+],[
+'🧦',
+'socks',
+'stocking stockings clothes'
+],[
+'👗',
+'dress',
+'clothing clothes fashion shopping'
+],[
+'👘',
+'kimono',
+'clothing dress fashion women female japanese'
+],[
+'🥻',
+'sari',
+'clothing dress'
+],[
+'🩱',
+'one-piece swimsuit',
+'bathing suit one piece swimsuit fashion'
+],[
+'🩲',
+'briefs',
+'bathing suit one-piece swimsuit underwear clothing'
+],[
+'🩳',
+'shorts',
+'bathing suit pants underwear clothing'
+],[
+'👙',
+'bikini',
+'clothing swim swimming female woman girl fashion beach summer'
+],[
+'👚',
+'woman’s clothes',
+'clothing woman woman s clothes fashion shopping bags female'
+],[
+'🪭',
+'folding hand fan',
+'cooling dance flutter folding hand fan hot shy buchaechum nihon buyō maranao'
+],[
+'👛',
+'purse',
+'clothing coin fashion accessories money sales shopping'
+],[
+'👜',
+'handbag',
+'bag clothing purse fashion accessory accessories shopping'
+],[
+'👝',
+'clutch bag',
+'bag clothing pouch accessories shopping'
+],[
+'🛍️',
+'shopping bags',
+'bag hotel shopping mall buy purchase'
+],[
+'🎒',
+'backpack',
+'bag rucksack satchel school student education'
+],[
+'🩴',
+'thong sandal',
+'beach sandals sandals thong sandals thongs zōri footwear summer'
+],[
+'👞',
+'man’s shoe',
+'clothing man shoe man s shoe fashion male'
+],[
+'👟',
+'running shoe',
+'athletic clothing shoe sneaker shoes sports sneakers'
+],[
+'🥾',
+'hiking boot',
+'backpacking boot camping hiking'
+],[
+'🥿',
+'flat shoe',
+'ballet flat slip-on slipper ballet'
+],[
+'👠',
+'high-heeled shoe',
+'clothing heel shoe woman high heeled shoe fashion shoes female pumps stiletto'
+],[
+'👡',
+'woman’s sandal',
+'clothing sandal shoe woman woman s sandal shoes fashion flip flops'
+],[
+'🩰',
+'ballet shoes',
+'ballet dance'
+],[
+'👢',
+'woman’s boot',
+'boot clothing shoe woman woman s boot shoes fashion'
+],[
+'🪮',
+'hair pick',
+'afro comb hair pick curly'
+],[
+'👑',
+'crown',
+'clothing king queen kod leader royalty lord'
+],[
+'👒',
+'woman’s hat',
+'clothing hat woman woman s hat fashion accessories female lady spring'
+],[
+'🎩',
+'top hat',
+'clothing hat top tophat magic gentleman classy circus'
+],[
+'🎓',
+'graduation cap',
+'cap celebration clothing graduation hat school college degree university legal learn education'
+],[
+'🧢',
+'billed cap',
+'baseball cap cap baseball'
+],[
+'🪖',
+'military helmet',
+'army helmet military soldier warrior protection'
+],[
+'⛑️',
+'rescue worker’s helmet',
+'aid cross face hat helmet rescue worker s helmet construction build'
+],[
+'📿',
+'prayer beads',
+'beads clothing necklace prayer religion dhikr religious'
+],[
+'💄',
+'lipstick',
+'cosmetics makeup female girl fashion woman'
+],[
+'💍',
+'ring',
+'diamond wedding propose marriage valentines fashion jewelry gem engagement'
+],[
+'💎',
+'gem stone',
+'diamond gem jewel blue ruby jewelry'
+],[
+'🔇',
+'muted speaker',
+'mute quiet silent speaker sound volume silence'
+],[
+'🔈',
+'speaker low volume',
+'soft sound volume silence broadcast'
+],[
+'🔉',
+'speaker medium volume',
+'medium volume speaker broadcast'
+],[
+'🔊',
+'speaker high volume',
+'loud volume noise noisy speaker broadcast'
+],[
+'📢',
+'loudspeaker',
+'loud public address volume sound'
+],[
+'📣',
+'megaphone',
+'cheering sound speaker volume'
+],[
+'📯',
+'postal horn',
+'horn post postal instrument music'
+],[
+'🔔',
+'bell',
+'sound notification christmas xmas chime'
+],[
+'🔕',
+'bell with slash',
+'bell forbidden mute quiet silent sound volume'
+],[
+'🎼',
+'musical score',
+'music score treble clef compose'
+],[
+'🎵',
+'musical note',
+'music note score tone sound'
+],[
+'🎶',
+'musical notes',
+'music note notes score'
+],[
+'🎙️',
+'studio microphone',
+'mic microphone music studio sing recording artist talkshow'
+],[
+'🎚️',
+'level slider',
+'level music slider scale'
+],[
+'🎛️',
+'control knobs',
+'control knobs music dial'
+],[
+'🎤',
+'microphone',
+'karaoke mic sound music PA sing talkshow'
+],[
+'🎧',
+'headphone',
+'earbud music score gadgets'
+],[
+'📻',
+'radio',
+'video communication music podcast program'
+],[
+'🎷',
+'saxophone',
+'instrument music sax jazz blues'
+],[
+'🪗',
+'accordion',
+'accordian concertina squeeze box music'
+],[
+'🎸',
+'guitar',
+'instrument music'
+],[
+'🎹',
+'musical keyboard',
+'instrument keyboard music piano compose'
+],[
+'🎺',
+'trumpet',
+'instrument music brass'
+],[
+'🎻',
+'violin',
+'instrument music orchestra symphony'
+],[
+'🪕',
+'banjo',
+'music stringed instructment'
+],[
+'🥁',
+'drum',
+'drumsticks music instrument snare'
+],[
+'🪘',
+'long drum',
+'beat conga drum rhythm music'
+],[
+'🪇',
+'maracas',
+'instrument maracas music percussion rattle shake'
+],[
+'🪈',
+'flute',
+'fife flute music pipe recorder woodwind'
+],[
+'',
+'harp',
+'cupid harp instrument love music orchestra'
+],[
+'📱',
+'mobile phone',
+'cell mobile phone telephone technology apple gadgets dial'
+],[
+'📲',
+'mobile phone with arrow',
+'arrow cell mobile phone receive iphone incoming'
+],[
+'☎️',
+'telephone',
+'phone technology communication dial'
+],[
+'📞',
+'telephone receiver',
+'phone receiver telephone technology communication dial'
+],[
+'📟',
+'pager',
+'bbcall oldschool 90s'
+],[
+'📠',
+'fax machine',
+'fax communication technology'
+],[
+'🔋',
+'battery',
+'power energy sustain'
+],[
+'🪫',
+'low battery',
+'electronic power low energy drained empty'
+],[
+'🔌',
+'electric plug',
+'electric electricity plug charger power'
+],[
+'💻',
+'laptop',
+'computer pc personal technology screen display monitor'
+],[
+'🖥️',
+'desktop computer',
+'computer desktop technology computing screen'
+],[
+'🖨️',
+'printer',
+'computer paper ink'
+],[
+'⌨️',
+'keyboard',
+'computer technology type input text'
+],[
+'🖱️',
+'computer mouse',
+'computer click'
+],[
+'🖲️',
+'trackball',
+'computer technology trackpad'
+],[
+'💽',
+'computer disk',
+'computer disk minidisk optical technology record data 90s'
+],[
+'💾',
+'floppy disk',
+'computer disk floppy oldschool technology save 90s 80s'
+],[
+'💿',
+'optical disk',
+'cd computer disk optical technology dvd disc 90s'
+],[
+'📀',
+'dvd',
+'blu-ray computer disk optical cd disc'
+],[
+'🧮',
+'abacus',
+'calculation'
+],[
+'🎥',
+'movie camera',
+'camera cinema movie film record'
+],[
+'🎞️',
+'film frames',
+'cinema film frames movie'
+],[
+'📽️',
+'film projector',
+'cinema film movie projector video tape record'
+],[
+'🎬',
+'clapper board',
+'clapper movie film record'
+],[
+'📺',
+'television',
+'tv video technology program oldschool show'
+],[
+'📷',
+'camera',
+'video gadgets photography'
+],[
+'📸',
+'camera with flash',
+'camera flash video photography gadgets'
+],[
+'📹',
+'video camera',
+'camera video film record'
+],[
+'📼',
+'videocassette',
+'tape vhs video record oldschool 90s 80s'
+],[
+'🔍',
+'magnifying glass tilted left',
+'glass magnifying search tool zoom find detective'
+],[
+'🔎',
+'magnifying glass tilted right',
+'glass magnifying search tool zoom find detective'
+],[
+'🕯️',
+'candle',
+'light fire wax'
+],[
+'💡',
+'light bulb',
+'bulb comic electric idea light electricity'
+],[
+'🔦',
+'flashlight',
+'electric light tool torch dark camping sight night'
+],[
+'🏮',
+'red paper lantern',
+'bar lantern light red paper halloween spooky'
+],[
+'🪔',
+'diya lamp',
+'diya lamp oil lighting'
+],[
+'📔',
+'notebook with decorative cover',
+'book cover decorated notebook classroom notes record paper study'
+],[
+'📕',
+'closed book',
+'book closed read library knowledge textbook learn'
+],[
+'📖',
+'open book',
+'book open read library knowledge literature learn study'
+],[
+'📗',
+'green book',
+'book green read library knowledge study'
+],[
+'📘',
+'blue book',
+'blue book read library knowledge learn study'
+],[
+'📙',
+'orange book',
+'book orange read library knowledge textbook study'
+],[
+'📚',
+'books',
+'book literature library study'
+],[
+'📓',
+'notebook',
+'stationery record notes paper study'
+],[
+'📒',
+'ledger',
+'notebook notes paper'
+],[
+'📃',
+'page with curl',
+'curl document page documents office paper'
+],[
+'📜',
+'scroll',
+'paper documents ancient history'
+],[
+'📄',
+'page facing up',
+'document page documents office paper information'
+],[
+'📰',
+'newspaper',
+'news paper press headline'
+],[
+'🗞️',
+'rolled-up newspaper',
+'news newspaper paper rolled rolled up newspaper press headline'
+],[
+'📑',
+'bookmark tabs',
+'bookmark mark marker tabs favorite save order tidy'
+],[
+'🔖',
+'bookmark',
+'mark favorite label save'
+],[
+'🏷️',
+'label',
+'sale tag'
+],[
+'💰',
+'money bag',
+'bag dollar money moneybag payment coins sale'
+],[
+'🪙',
+'coin',
+'gold metal money silver treasure currency'
+],[
+'💴',
+'yen banknote',
+'banknote bill currency money note yen sales japanese dollar'
+],[
+'💵',
+'dollar banknote',
+'banknote bill currency dollar money note sales'
+],[
+'💶',
+'euro banknote',
+'banknote bill currency euro money note sales dollar'
+],[
+'💷',
+'pound banknote',
+'banknote bill currency money note pound british sterling sales bills uk england'
+],[
+'💸',
+'money with wings',
+'banknote bill fly money wings dollar bills payment sale'
+],[
+'💳',
+'credit card',
+'card credit money sales dollar bill payment shopping'
+],[
+'🧾',
+'receipt',
+'accounting bookkeeping evidence proof expenses'
+],[
+'💹',
+'chart increasing with yen',
+'chart graph growth money yen green-square presentation stats'
+],[
+'✉️',
+'envelope',
+'email letter postal inbox communication'
+],[
+'📧',
+'e-mail',
+'email letter mail e mail communication inbox'
+],[
+'📨',
+'incoming envelope',
+'e-mail email envelope incoming letter receive inbox'
+],[
+'📩',
+'envelope with arrow',
+'arrow e-mail email envelope outgoing communication'
+],[
+'📤',
+'outbox tray',
+'box letter mail outbox sent tray inbox email'
+],[
+'📥',
+'inbox tray',
+'box inbox letter mail receive tray email documents'
+],[
+'📦',
+'package',
+'box parcel mail gift cardboard moving'
+],[
+'📫',
+'closed mailbox with raised flag',
+'closed mail mailbox postbox email inbox communication'
+],[
+'📪',
+'closed mailbox with lowered flag',
+'closed lowered mail mailbox postbox email communication inbox'
+],[
+'📬',
+'open mailbox with raised flag',
+'mail mailbox open postbox email inbox communication'
+],[
+'📭',
+'open mailbox with lowered flag',
+'lowered mail mailbox open postbox email inbox'
+],[
+'📮',
+'postbox',
+'mail mailbox email letter envelope'
+],[
+'🗳️',
+'ballot box with ballot',
+'ballot box election vote'
+],[
+'✏️',
+'pencil',
+'stationery write paper writing school study'
+],[
+'✒️',
+'black nib',
+'nib pen stationery writing write'
+],[
+'🖋️',
+'fountain pen',
+'fountain pen stationery writing write'
+],[
+'🖊️',
+'pen',
+'ballpoint stationery writing write'
+],[
+'🖌️',
+'paintbrush',
+'painting drawing creativity art'
+],[
+'🖍️',
+'crayon',
+'drawing creativity'
+],[
+'📝',
+'memo',
+'pencil write documents stationery paper writing legal exam quiz test study compose'
+],[
+'💼',
+'briefcase',
+'business documents work law legal job career'
+],[
+'📁',
+'file folder',
+'file folder documents business office'
+],[
+'📂',
+'open file folder',
+'file folder open documents load'
+],[
+'🗂️',
+'card index dividers',
+'card dividers index organizing business stationery'
+],[
+'📅',
+'calendar',
+'date schedule'
+],[
+'📆',
+'tear-off calendar',
+'calendar tear off calendar schedule date planning'
+],[
+'🗒️',
+'spiral notepad',
+'note pad spiral memo stationery'
+],[
+'🗓️',
+'spiral calendar',
+'calendar pad spiral date schedule planning'
+],[
+'📇',
+'card index',
+'card index rolodex business stationery'
+],[
+'📈',
+'chart increasing',
+'chart graph growth trend upward presentation stats recovery business economics money sales good success'
+],[
+'📉',
+'chart decreasing',
+'chart down graph trend presentation stats recession business economics money sales bad failure'
+],[
+'📊',
+'bar chart',
+'bar chart graph presentation stats'
+],[
+'📋',
+'clipboard',
+'stationery documents'
+],[
+'📌',
+'pushpin',
+'pin stationery mark here'
+],[
+'📍',
+'round pushpin',
+'pin pushpin stationery location map here'
+],[
+'📎',
+'paperclip',
+'documents stationery'
+],[
+'🖇️',
+'linked paperclips',
+'link paperclip documents stationery'
+],[
+'📏',
+'straight ruler',
+'ruler straight edge stationery calculate length math school drawing architect sketch'
+],[
+'📐',
+'triangular ruler',
+'ruler set triangle stationery math architect sketch'
+],[
+'✂️',
+'scissors',
+'cutting tool stationery cut'
+],[
+'🗃️',
+'card file box',
+'box card file business stationery'
+],[
+'🗄️',
+'file cabinet',
+'cabinet file filing organizing'
+],[
+'🗑️',
+'wastebasket',
+'bin trash rubbish garbage toss'
+],[
+'🔒',
+'locked',
+'closed security password padlock'
+],[
+'🔓',
+'unlocked',
+'lock open unlock privacy security'
+],[
+'🔏',
+'locked with pen',
+'ink lock nib pen privacy security secret'
+],[
+'🔐',
+'locked with key',
+'closed key lock secure security privacy'
+],[
+'🔑',
+'key',
+'lock password door'
+],[
+'🗝️',
+'old key',
+'clue key lock old door password'
+],[
+'🔨',
+'hammer',
+'tool tools build create'
+],[
+'🪓',
+'axe',
+'chop hatchet split wood tool cut'
+],[
+'⛏️',
+'pick',
+'mining tool tools dig'
+],[
+'⚒️',
+'hammer and pick',
+'hammer pick tool tools build create'
+],[
+'🛠️',
+'hammer and wrench',
+'hammer spanner tool wrench tools build create'
+],[
+'🗡️',
+'dagger',
+'knife weapon'
+],[
+'⚔️',
+'crossed swords',
+'crossed swords weapon'
+],[
+'💣',
+'bomb',
+'comic boom explode explosion terrorism'
+],[
+'🪃',
+'boomerang',
+'australia rebound repercussion weapon'
+],[
+'🏹',
+'bow and arrow',
+'archer arrow bow Sagittarius zodiac sports'
+],[
+'🛡️',
+'shield',
+'weapon protection security'
+],[
+'🪚',
+'carpentry saw',
+'carpenter lumber saw tool cut chop'
+],[
+'🔧',
+'wrench',
+'spanner tool tools diy ikea fix maintainer'
+],[
+'🪛',
+'screwdriver',
+'screw tool tools'
+],[
+'🔩',
+'nut and bolt',
+'bolt nut tool handy tools fix'
+],[
+'⚙️',
+'gear',
+'cog cogwheel tool'
+],[
+'🗜️',
+'clamp',
+'compress tool vice'
+],[
+'⚖️',
+'balance scale',
+'balance justice Libra scale zodiac law fairness weight'
+],[
+'🦯',
+'white cane',
+'accessibility blind probing cane'
+],[
+'🔗',
+'link',
+'chain rings url hyperlink'
+],[
+'⛓️💥',
+'broken chain',
+'breaking broken chain cuffs freedom'
+],[
+'⛓️',
+'chains',
+'chain lock arrest'
+],[
+'🪝',
+'hook',
+'catch crook curve ensnare selling point tools'
+],[
+'🧰',
+'toolbox',
+'chest mechanic tool tools diy fix maintainer'
+],[
+'🧲',
+'magnet',
+'attraction horseshoe magnetic'
+],[
+'🪜',
+'ladder',
+'climb rung step tools'
+],[
+'',
+'shovel',
+'bury dig garden hole plant scoop shovel snow spade'
+],[
+'⚗️',
+'alembic',
+'chemistry tool distilling science experiment'
+],[
+'🧪',
+'test tube',
+'chemist chemistry experiment lab science'
+],[
+'🧫',
+'petri dish',
+'bacteria biologist biology culture lab'
+],[
+'🧬',
+'dna',
+'biologist evolution gene genetics life'
+],[
+'🔬',
+'microscope',
+'science tool laboratory experiment zoomin study'
+],[
+'🔭',
+'telescope',
+'science tool stars space zoom astronomy'
+],[
+'📡',
+'satellite antenna',
+'antenna dish satellite communication future radio space'
+],[
+'💉',
+'syringe',
+'medicine needle shot sick health hospital drugs blood doctor nurse'
+],[
+'🩸',
+'drop of blood',
+'bleed blood donation injury medicine menstruation period hurt harm wound'
+],[
+'💊',
+'pill',
+'doctor medicine sick health pharmacy drug'
+],[
+'🩹',
+'adhesive bandage',
+'bandage heal'
+],[
+'🩼',
+'crutch',
+'cane disability hurt mobility aid stick'
+],[
+'🩺',
+'stethoscope',
+'doctor heart medicine health'
+],[
+'🩻',
+'x-ray',
+'bones doctor medical skeleton'
+],[
+'🚪',
+'door',
+'house entry exit'
+],[
+'🛗',
+'elevator',
+'accessibility hoist lift'
+],[
+'🪞',
+'mirror',
+'reflection reflector speculum'
+],[
+'🪟',
+'window',
+'frame fresh air opening transparent view scenery'
+],[
+'🛏️',
+'bed',
+'hotel sleep rest'
+],[
+'🛋️',
+'couch and lamp',
+'couch hotel lamp read chill'
+],[
+'🪑',
+'chair',
+'seat sit furniture'
+],[
+'🚽',
+'toilet',
+'restroom wc washroom bathroom potty'
+],[
+'🪠',
+'plunger',
+'force cup plumber suction toilet'
+],[
+'🚿',
+'shower',
+'water clean bathroom'
+],[
+'🛁',
+'bathtub',
+'bath clean shower bathroom'
+],[
+'🪤',
+'mouse trap',
+'bait mousetrap snare trap cheese'
+],[
+'🪒',
+'razor',
+'sharp shave cut'
+],[
+'🧴',
+'lotion bottle',
+'lotion moisturizer shampoo sunscreen'
+],[
+'🧷',
+'safety pin',
+'diaper punk rock'
+],[
+'🧹',
+'broom',
+'cleaning sweeping witch'
+],[
+'🧺',
+'basket',
+'farming laundry picnic'
+],[
+'🧻',
+'roll of paper',
+'paper towels toilet paper roll'
+],[
+'🪣',
+'bucket',
+'cask pail vat water container'
+],[
+'🧼',
+'soap',
+'bar bathing cleaning lather soapdish'
+],[
+'🫧',
+'bubbles',
+'burp clean soap underwater'
+],[
+'🪥',
+'toothbrush',
+'bathroom brush clean dental hygiene teeth'
+],[
+'🧽',
+'sponge',
+'absorbing cleaning porous'
+],[
+'🧯',
+'fire extinguisher',
+'extinguish fire quench'
+],[
+'🛒',
+'shopping cart',
+'cart shopping trolley'
+],[
+'🚬',
+'cigarette',
+'smoking kills tobacco joint smoke'
+],[
+'⚰️',
+'coffin',
+'death vampire dead die rip graveyard cemetery casket funeral box'
+],[
+'🪦',
+'headstone',
+'cemetery grave graveyard tombstone death rip'
+],[
+'⚱️',
+'funeral urn',
+'ashes death funeral urn dead die rip'
+],[
+'🧿',
+'nazar amulet',
+'bead charm evil-eye nazar talisman'
+],[
+'🪬',
+'hamsa',
+'amulet Fatima hand Mary Miriam protection'
+],[
+'🗿',
+'moai',
+'face moyai statue rock easter island'
+],[
+'🪧',
+'placard',
+'demonstration picket protest sign announcement'
+],[
+'🪪',
+'identification card',
+'credentials ID license security'
+],
+
+
+
+// Symbols
+[
+'🏧',
+'ATM sign',
+'atm automated bank teller atm sign money sales cash blue-square payment'
+],[
+'🚮',
+'litter in bin sign',
+'litter litter bin blue-square sign human info'
+],[
+'🚰',
+'potable water',
+'drinking potable water blue-square liquid restroom cleaning faucet'
+],[
+'♿',
+'wheelchair symbol',
+'access blue-square disabled accessibility'
+],[
+'🚹',
+'men’s room',
+'lavatory man restroom wc men s room toilet blue-square gender male'
+],[
+'🚺',
+'women’s room',
+'lavatory restroom wc woman women s room purple-square female toilet loo gender'
+],[
+'🚻',
+'restroom',
+'lavatory WC blue-square toilet refresh wc gender'
+],[
+'🚼',
+'baby symbol',
+'baby changing orange-square child'
+],[
+'🚾',
+'water closet',
+'closet lavatory restroom water wc toilet blue-square'
+],[
+'🛂',
+'passport control',
+'control passport custom blue-square'
+],[
+'🛃',
+'customs',
+'passport border blue-square'
+],[
+'🛄',
+'baggage claim',
+'baggage claim blue-square airport transport'
+],[
+'🛅',
+'left luggage',
+'baggage locker luggage blue-square travel'
+],[
+'⚠️',
+'warning',
+'exclamation wip alert error problem issue'
+],[
+'🚸',
+'children crossing',
+'child crossing pedestrian traffic school warning danger sign driving yellow-diamond'
+],[
+'⛔',
+'no entry',
+'entry forbidden no not prohibited traffic limit security privacy bad denied stop circle'
+],[
+'🚫',
+'prohibited',
+'entry forbidden no not forbid stop limit denied disallow circle'
+],[
+'🚳',
+'no bicycles',
+'bicycle bike forbidden no prohibited cyclist circle'
+],[
+'🚭',
+'no smoking',
+'forbidden no not prohibited smoking cigarette blue-square smell smoke'
+],[
+'🚯',
+'no littering',
+'forbidden litter no not prohibited trash bin garbage circle'
+],[
+'🚱',
+'non-potable water',
+'non-drinking non-potable water non potable water drink faucet tap circle'
+],[
+'🚷',
+'no pedestrians',
+'forbidden no not pedestrian prohibited rules crossing walking circle'
+],[
+'📵',
+'no mobile phones',
+'cell forbidden mobile no phone iphone mute circle'
+],[
+'🔞',
+'no one under eighteen',
+'18 age restriction eighteen prohibited underage drink pub night minor circle'
+],[
+'☢️',
+'radioactive',
+'sign nuclear danger'
+],[
+'☣️',
+'biohazard',
+'sign danger'
+],[
+'⬆️',
+'up arrow',
+'arrow cardinal direction north blue-square continue top'
+],[
+'↗️',
+'up-right arrow',
+'arrow direction intercardinal northeast up right arrow blue-square point diagonal'
+],[
+'➡️',
+'right arrow',
+'arrow cardinal direction east blue-square next'
+],[
+'↘️',
+'down-right arrow',
+'arrow direction intercardinal southeast down right arrow blue-square diagonal'
+],[
+'⬇️',
+'down arrow',
+'arrow cardinal direction down south blue-square bottom'
+],[
+'↙️',
+'down-left arrow',
+'arrow direction intercardinal southwest down left arrow blue-square diagonal'
+],[
+'⬅️',
+'left arrow',
+'arrow cardinal direction west blue-square previous back'
+],[
+'↖️',
+'up-left arrow',
+'arrow direction intercardinal northwest up left arrow blue-square point diagonal'
+],[
+'↕️',
+'up-down arrow',
+'arrow up down arrow blue-square direction way vertical'
+],[
+'↔️',
+'left-right arrow',
+'arrow left right arrow shape direction horizontal sideways'
+],[
+'↩️',
+'right arrow curving left',
+'arrow back return blue-square undo enter'
+],[
+'↪️',
+'left arrow curving right',
+'arrow blue-square return rotate direction'
+],[
+'⤴️',
+'right arrow curving up',
+'arrow blue-square direction top'
+],[
+'⤵️',
+'right arrow curving down',
+'arrow down blue-square direction bottom'
+],[
+'🔃',
+'clockwise vertical arrows',
+'arrow clockwise reload sync cycle round repeat'
+],[
+'🔄',
+'counterclockwise arrows button',
+'anticlockwise arrow counterclockwise withershins blue-square sync cycle'
+],[
+'🔙',
+'BACK arrow',
+'arrow back back arrow words return'
+],[
+'🔚',
+'END arrow',
+'arrow end end arrow words'
+],[
+'🔛',
+'ON! arrow',
+'arrow mark on on arrow words'
+],[
+'🔜',
+'SOON arrow',
+'arrow soon soon arrow words'
+],[
+'🔝',
+'TOP arrow',
+'arrow top up top arrow words blue-square'
+],[
+'🛐',
+'place of worship',
+'religion worship church temple prayer'
+],[
+'⚛️',
+'atom symbol',
+'atheist atom science physics chemistry'
+],[
+'🕉️',
+'om',
+'Hindu religion hinduism buddhism sikhism jainism'
+],[
+'✡️',
+'star of David',
+'David Jew Jewish religion star star of david judaism'
+],[
+'☸️',
+'wheel of dharma',
+'Buddhist dharma religion wheel hinduism buddhism sikhism jainism'
+],[
+'☯️',
+'yin yang',
+'religion tao taoist yang yin balance'
+],[
+'✝️',
+'latin cross',
+'Christian cross religion christianity'
+],[
+'☦️',
+'orthodox cross',
+'Christian cross religion suppedaneum'
+],[
+'☪️',
+'star and crescent',
+'islam Muslim religion'
+],[
+'☮️',
+'peace symbol',
+'peace hippie'
+],[
+'🕎',
+'menorah',
+'candelabrum candlestick religion hanukkah candles jewish'
+],[
+'🔯',
+'dotted six-pointed star',
+'fortune star dotted six pointed star purple-square religion jewish hexagram'
+],[
+'🪯',
+'khanda',
+'khanda religion Sikh'
+],[
+'♈',
+'Aries',
+'ram zodiac aries sign purple-square astrology'
+],[
+'♉',
+'Taurus',
+'bull ox zodiac taurus purple-square sign astrology'
+],[
+'♊',
+'Gemini',
+'twins zodiac gemini sign purple-square astrology'
+],[
+'♋',
+'Cancer',
+'crab zodiac cancer sign purple-square astrology'
+],[
+'♌',
+'Leo',
+'lion zodiac leo sign purple-square astrology'
+],[
+'♍',
+'Virgo',
+'zodiac virgo sign purple-square astrology'
+],[
+'♎',
+'Libra',
+'balance justice scales zodiac libra sign purple-square astrology'
+],[
+'♏',
+'Scorpio',
+'scorpion scorpius zodiac scorpio sign purple-square astrology'
+],[
+'♐',
+'Sagittarius',
+'archer zodiac sagittarius sign purple-square astrology'
+],[
+'♑',
+'Capricorn',
+'goat zodiac capricorn sign purple-square astrology'
+],[
+'♒',
+'Aquarius',
+'bearer water zodiac aquarius sign purple-square astrology'
+],[
+'♓',
+'Pisces',
+'fish zodiac pisces purple-square sign astrology'
+],[
+'⛎',
+'Ophiuchus',
+'bearer serpent snake zodiac ophiuchus sign purple-square constellation astrology'
+],[
+'🔀',
+'shuffle tracks button',
+'arrow crossed blue-square shuffle music random'
+],[
+'🔁',
+'repeat button',
+'arrow clockwise repeat loop record'
+],[
+'🔂',
+'repeat single button',
+'arrow clockwise once blue-square loop'
+],[
+'▶️',
+'play button',
+'arrow play right triangle blue-square direction'
+],[
+'⏩',
+'fast-forward button',
+'arrow double fast forward fast forward button blue-square play speed continue'
+],[
+'⏭️',
+'next track button',
+'arrow next scene next track triangle forward next blue-square'
+],[
+'⏭️',
+'play or pause button',
+'arrow pause play right triangle blue-square'
+],[
+'◀️',
+'reverse button',
+'arrow left reverse triangle blue-square direction'
+],[
+'⏪',
+'fast reverse button',
+'arrow double rewind play blue-square'
+],[
+'⏮️',
+'last track button',
+'arrow previous scene previous track triangle backward'
+],[
+'🔼',
+'upwards button',
+'arrow button red blue-square triangle direction point forward top'
+],[
+'⏫',
+'fast up button',
+'arrow double blue-square direction top'
+],[
+'🔽',
+'downwards button',
+'arrow button down red blue-square direction bottom'
+],[
+'⏬',
+'fast down button',
+'arrow double down blue-square direction bottom'
+],[
+'⏸️',
+'pause button',
+'bar double pause vertical blue-square'
+],[
+'⏹️',
+'stop button',
+'square stop blue-square'
+],[
+'⏺️',
+'record button',
+'circle record blue-square'
+],[
+'⏏️',
+'eject button',
+'eject blue-square'
+],[
+'🎦',
+'cinema',
+'camera film movie blue-square record curtain stage theater'
+],[
+'🔅',
+'dim button',
+'brightness dim low sun afternoon warm summer'
+],[
+'🔆',
+'bright button',
+'bright brightness sun light'
+],[
+'📶',
+'antenna bars',
+'antenna bar cell mobile phone blue-square reception internet connection wifi bluetooth bars'
+],[
+'🛜',
+'wireless',
+'computer internet network wi-fi wifi wireless connection'
+],[
+'📳',
+'vibration mode',
+'cell mobile mode phone telephone vibration orange-square'
+],[
+'📴',
+'mobile phone off',
+'cell mobile off phone telephone mute orange-square silence quiet'
+],[
+'♀️',
+'female sign',
+'woman women lady girl'
+],[
+'♂️',
+'male sign',
+'man boy men'
+],[
+'⚧️',
+'transgender symbol',
+'transgender lgbtq'
+],[
+'✖️',
+'multiply',
+'× cancel multiplication sign x multiplication sign math calculation'
+],[
+'➕',
+'plus',
+'+ math sign plus sign calculation addition more increase'
+],[
+'➖',
+'minus',
+'- − math sign minus sign calculation subtract less'
+],[
+'➗',
+'divide',
+'÷ division math sign division sign calculation'
+],[
+'🟰',
+'heavy equals sign',
+'equality math equation'
+],[
+'♾️',
+'infinity',
+'forever unbounded universal'
+],[
+'‼️',
+'double exclamation mark',
+'! !! bangbang exclamation mark surprise'
+],[
+'⁉️',
+'exclamation question mark',
+'! !? ? exclamation interrobang mark punctuation question wat surprise'
+],[
+'❓',
+'red question mark',
+'? mark punctuation question question mark doubt confused'
+],[
+'❔',
+'white question mark',
+'? mark outlined punctuation question doubts gray huh confused'
+],[
+'❕',
+'white exclamation mark',
+'! exclamation mark outlined punctuation surprise gray wow warning'
+],[
+'❗',
+'red exclamation mark',
+'! exclamation mark punctuation exclamation mark heavy exclamation mark danger surprise wow warning'
+],[
+'〰️',
+'wavy dash',
+'dash punctuation wavy draw line moustache mustache squiggle scribble'
+],[
+'💱',
+'currency exchange',
+'bank currency exchange money sales dollar travel'
+],[
+'💲',
+'heavy dollar sign',
+'currency dollar money sales payment buck'
+],[
+'⚕️',
+'medical symbol',
+'aesculapius medicine staff health hospital'
+],[
+'♻️',
+'recycling symbol',
+'recycle arrow environment garbage trash'
+],[
+'⚜️',
+'fleur-de-lis',
+'fleur de lis decorative scout'
+],[
+'🔱',
+'trident emblem',
+'anchor emblem ship tool trident weapon spear'
+],[
+'📛',
+'name badge',
+'badge name fire forbid'
+],[
+'🔰',
+'Japanese symbol for beginner',
+'beginner chevron Japanese leaf japanese symbol for beginner badge shield'
+],[
+'⭕',
+'hollow red circle',
+'circle large o red round'
+],[
+'✅',
+'check mark button',
+'✓ button check mark green-square ok agree vote election answer tick'
+],[
+'☑️',
+'check box with check',
+'✓ box check ok agree confirm black-square vote election yes tick'
+],[
+'✔️',
+'check mark',
+'✓ check mark ok nike answer yes tick'
+],[
+'❌',
+'cross mark',
+'× cancel cross mark multiplication multiply x no delete remove red'
+],[
+'❎',
+'cross mark button',
+'× mark square x green-square no deny'
+],[
+'➰',
+'curly loop',
+'curl loop scribble draw shape squiggle'
+],[
+'➿',
+'double curly loop',
+'curl double loop tape cassette'
+],[
+'〽️',
+'part alternation mark',
+'mark part graph presentation stats business economics bad'
+],[
+'✳️',
+'eight-spoked asterisk',
+'* asterisk eight spoked asterisk star sparkle green-square'
+],[
+'✴️',
+'eight-pointed star',
+'* star eight pointed star orange-square shape polygon'
+],[
+'❇️',
+'sparkle',
+'* stars green-square awesome good fireworks'
+],[
+'©️',
+'copyright',
+'c ip license circle law legal'
+],[
+'®️',
+'registered',
+'r alphabet circle'
+],[
+'™️',
+'trade mark',
+'mark tm trademark brand law legal'
+],[
+'',
+'splatter',
+'drip holi ink liquid mess paint spill stain'
+],[
+'#️⃣',
+'keycap: #',
+'keycap keycap symbol blue-square twitter'
+],[
+'*️⃣',
+'keycap: *',
+'keycap keycap star'
+],[
+'0️⃣',
+'keycap: 0',
+'keycap keycap 0 0 numbers blue-square null'
+],[
+'1️⃣',
+'keycap: 1',
+'keycap keycap 1 blue-square numbers 1'
+],[
+'2️⃣',
+'keycap: 2',
+'keycap keycap 2 numbers 2 prime blue-square'
+],[
+'3️⃣',
+'keycap: 3',
+'keycap keycap 3 3 numbers prime blue-square'
+],[
+'4️⃣',
+'keycap: 4',
+'keycap keycap 4 4 numbers blue-square'
+],[
+'5️⃣',
+'keycap: 5',
+'keycap keycap 5 5 numbers blue-square prime'
+],[
+'6️⃣',
+'keycap: 6',
+'keycap keycap 6 6 numbers blue-square'
+],[
+'7️⃣',
+'keycap: 7',
+'keycap keycap 7 7 numbers blue-square prime'
+],[
+'8️⃣',
+'keycap: 8',
+'keycap keycap 8 8 blue-square numbers'
+],[
+'9️⃣',
+'keycap: 9',
+'keycap keycap 9 blue-square numbers 9'
+],[
+'🔟',
+'keycap: 10',
+'keycap keycap 10 numbers 10 blue-square'
+],[
+'🔠',
+'input latin uppercase',
+'ABCD input latin letters uppercase alphabet words blue-square'
+],[
+'🔡',
+'input latin lowercase',
+'abcd input latin letters lowercase blue-square alphabet'
+],[
+'🔢',
+'input numbers',
+'1234 input numbers blue-square'
+],[
+'🔣',
+'input symbols',
+'〒♪&% input blue-square music note ampersand percent glyphs characters'
+],[
+'🔤',
+'input latin letters',
+'abc alphabet input latin letters blue-square'
+],[
+'🅰️',
+'A button (blood type)',
+'a blood type a button red-square alphabet letter'
+],[
+'🆎',
+'AB button (blood type)',
+'ab blood type ab button red-square alphabet'
+],[
+'🅱️',
+'B button (blood type)',
+'b blood type b button red-square alphabet letter'
+],[
+'🆑',
+'CL button',
+'cl cl button alphabet words red-square'
+],[
+'🆒',
+'COOL button',
+'cool cool button words blue-square'
+],[
+'🆓',
+'FREE button',
+'free free button blue-square words'
+],[
+'ℹ️',
+'information',
+'i blue-square alphabet letter'
+],[
+'🆔',
+'ID button',
+'id identity id button purple-square words'
+],[
+'Ⓜ️',
+'circled M',
+'circle m circled m alphabet blue-circle letter'
+],[
+'🆕',
+'NEW button',
+'new new button blue-square words start'
+],[
+'🆖',
+'NG button',
+'ng ng button blue-square words shape icon'
+],[
+'🅾️',
+'O button (blood type)',
+'blood type o o button alphabet red-square letter'
+],[
+'🆗',
+'OK button',
+'OK ok button good agree yes blue-square'
+],[
+'🅿️',
+'P button',
+'parking p button cars blue-square alphabet letter'
+],[
+'🆘',
+'SOS button',
+'help sos sos button red-square words emergency 911'
+],[
+'🆙',
+'UP! button',
+'mark up up button blue-square above high'
+],[
+'🆚',
+'VS button',
+'versus vs vs button words orange-square'
+],[
+'🈁',
+'Japanese “here” button',
+'“here” Japanese katakana ココ japanese here button blue-square here japanese destination'
+],[
+'🈂️',
+'Japanese “service charge” button',
+'“service charge” Japanese katakana サ japanese service charge button japanese blue-square'
+],[
+'🈷️',
+'Japanese “monthly amount” button',
+'“monthly amount” ideograph Japanese 月 japanese monthly amount button chinese month moon japanese orange-square kanji'
+],[
+'🈶',
+'Japanese “not free of charge” button',
+'“not free of charge” ideograph Japanese 有 japanese not free of charge button orange-square chinese have kanji'
+],[
+'🈯',
+'Japanese “reserved” button',
+'“reserved” ideograph Japanese 指 japanese reserved button chinese point green-square kanji'
+],[
+'🉐',
+'Japanese “bargain” button',
+'“bargain” ideograph Japanese 得 japanese bargain button chinese kanji obtain get circle'
+],[
+'🈹',
+'Japanese “discount” button',
+'“discount” ideograph Japanese 割 japanese discount button cut divide chinese kanji pink-square'
+],[
+'🈚',
+'Japanese “free of charge” button',
+'“free of charge” ideograph Japanese 無 japanese free of charge button nothing chinese kanji japanese orange-square'
+],[
+'🈲',
+'Japanese “prohibited” button',
+'“prohibited” ideograph Japanese 禁 japanese prohibited button kanji japanese chinese forbidden limit restricted red-square'
+],[
+'🉑',
+'Japanese “acceptable” button',
+'“acceptable” ideograph Japanese 可 japanese acceptable button ok good chinese kanji agree yes orange-circle'
+],[
+'🈸',
+'Japanese “application” button',
+'“application” ideograph Japanese 申 japanese application button chinese japanese kanji orange-square'
+],[
+'🈴',
+'Japanese “passing grade” button',
+'“passing grade” ideograph Japanese 合 japanese passing grade button japanese chinese join kanji red-square'
+],[
+'🈳',
+'Japanese “vacancy” button',
+'“vacancy” ideograph Japanese 空 japanese vacancy button kanji japanese chinese empty sky blue-square'
+],[
+'㊗️',
+'Japanese “congratulations” button',
+'“congratulations” ideograph Japanese 祝 japanese congratulations button chinese kanji japanese red-circle'
+],[
+'㊙️',
+'Japanese “secret” button',
+'“secret” ideograph Japanese 秘 japanese secret button privacy chinese sshh kanji red-circle'
+],[
+'🈺',
+'Japanese “open for business” button',
+'“open for business” ideograph Japanese 営 japanese open for business button japanese opening hours orange-square'
+],[
+'🈵',
+'Japanese “no vacancy” button',
+'“no vacancy” ideograph Japanese 満 japanese no vacancy button full chinese japanese red-square kanji'
+],[
+'🔴',
+'red circle',
+'circle geometric red shape error danger'
+],[
+'🟠',
+'orange circle',
+'circle orange round'
+],[
+'🟡',
+'yellow circle',
+'circle yellow round'
+],[
+'🟢',
+'green circle',
+'circle green round'
+],[
+'🔵',
+'blue circle',
+'blue circle geometric shape icon button'
+],[
+'🟣',
+'purple circle',
+'circle purple round'
+],[
+'🟤',
+'brown circle',
+'brown circle round'
+],[
+'⚫',
+'black circle',
+'circle geometric shape button round'
+],[
+'⚪',
+'white circle',
+'circle geometric shape round'
+],[
+'🟥',
+'red square',
+'red square'
+],[
+'🟧',
+'orange square',
+'orange square'
+],[
+'🟨',
+'yellow square',
+'square yellow'
+],[
+'🟩',
+'green square',
+'green square'
+],[
+'🟦',
+'blue square',
+'blue square'
+],[
+'🟪',
+'purple square',
+'purple square'
+],[
+'🟫',
+'brown square',
+'brown square'
+],[
+'⬛',
+'black large square',
+'geometric square shape icon button'
+],[
+'⬜',
+'white large square',
+'geometric square shape icon stone button'
+],[
+'◼️',
+'black medium square',
+'geometric square shape button icon'
+],[
+'◻️',
+'white medium square',
+'geometric square shape stone icon'
+],[
+'◾',
+'black medium-small square',
+'geometric square black medium small square icon shape button'
+],[
+'◽',
+'white medium-small square',
+'geometric square white medium small square shape stone icon button'
+],[
+'▪️',
+'black small square',
+'geometric square shape icon'
+],[
+'▫️',
+'white small square',
+'geometric square shape icon'
+],[
+'🔶',
+'large orange diamond',
+'diamond geometric orange shape jewel gem'
+],[
+'🔷',
+'large blue diamond',
+'blue diamond geometric shape jewel gem'
+],[
+'🔸',
+'small orange diamond',
+'diamond geometric orange shape jewel gem'
+],[
+'🔹',
+'small blue diamond',
+'blue diamond geometric shape jewel gem'
+],[
+'🔺',
+'red triangle pointed up',
+'geometric red shape direction up top'
+],[
+'🔻',
+'red triangle pointed down',
+'down geometric red shape direction bottom'
+],[
+'💠',
+'diamond with a dot',
+'comic diamond geometric inside jewel blue gem crystal fancy'
+],[
+'🔘',
+'radio button',
+'button geometric radio input old music circle'
+],[
+'🔳',
+'white square button',
+'button geometric outlined square shape input'
+],[
+'🔲',
+'black square button',
+'button geometric square shape input frame'
+],
+
+
+
+// Flags
+[
+'🏁',
+'chequered flag',
+'checkered chequered racing contest finishline race gokart'
+],[
+'🚩',
+'triangular flag',
+'post mark milestone place'
+],[
+'🎌',
+'crossed flags',
+'celebration cross crossed Japanese japanese nation country border'
+],[
+'🏴',
+'black flag',
+'waving pirate'
+],[
+'🏳️',
+'white flag',
+'waving losing loser lost surrender give up fail'
+],[
+'🏳️🌈',
+'rainbow flag',
+'pride rainbow flag gay lgbt glbt queer homosexual lesbian bisexual transgender'
+],[
+'🏳️⚧️',
+'transgender flag',
+'flag light blue pink transgender white lgbtq'
+],[
+'🏴☠️',
+'pirate flag',
+'Jolly Roger pirate plunder treasure skull crossbones flag banner'
+],[
+'🇦🇨',
+'flag: Ascension Island',
+'flag flag ascension island'
+],[
+'🇦🇩',
+'flag: Andorra',
+'flag flag andorra ad nation country banner'
+],[
+'🇦🇪',
+'flag: United Arab Emirates',
+'flag flag united arab emirates united arab emirates nation country banner'
+],[
+'🇦🇫',
+'flag: Afghanistan',
+'flag flag afghanistan af nation country banner'
+],[
+'🇦🇬',
+'flag: Antigua & Barbuda',
+'flag flag antigua barbuda antigua barbuda nation country banner'
+],[
+'🇦🇮',
+'flag: Anguilla',
+'flag flag anguilla ai nation country banner'
+],[
+'🇦🇱',
+'flag: Albania',
+'flag flag albania al nation country banner'
+],[
+'🇦🇲',
+'flag: Armenia',
+'flag flag armenia am nation country banner'
+],[
+'🇦🇴',
+'flag: Angola',
+'flag flag angola ao nation country banner'
+],[
+'🇦🇶',
+'flag: Antarctica',
+'flag flag antarctica aq nation country banner'
+],[
+'🇦🇷',
+'flag: Argentina',
+'flag flag argentina ar nation country banner'
+],[
+'🇦🇸',
+'flag: American Samoa',
+'flag flag american samoa american ws nation country banner'
+],[
+'🇦🇹',
+'flag: Austria',
+'flag flag austria at nation country banner'
+],[
+'🇦🇺',
+'flag: Australia',
+'flag flag australia au nation country banner'
+],[
+'🇦🇼',
+'flag: Aruba',
+'flag flag aruba aw nation country banner'
+],[
+'🇦🇽',
+'flag: Åland Islands',
+'flag flag aland islands Åland islands nation country banner'
+],[
+'🇦🇿',
+'flag: Azerbaijan',
+'flag flag azerbaijan az nation country banner'
+],[
+'🇧🇦',
+'flag: Bosnia & Herzegovina',
+'flag flag bosnia herzegovina bosnia herzegovina nation country banner'
+],[
+'🇧🇧',
+'flag: Barbados',
+'flag flag barbados bb nation country banner'
+],[
+'🇧🇩',
+'flag: Bangladesh',
+'flag flag bangladesh bd nation country banner'
+],[
+'🇧🇪',
+'flag: Belgium',
+'flag flag belgium be nation country banner'
+],[
+'🇧🇫',
+'flag: Burkina Faso',
+'flag flag burkina faso burkina faso nation country banner'
+],[
+'🇧🇬',
+'flag: Bulgaria',
+'flag flag bulgaria bg nation country banner'
+],[
+'🇧🇭',
+'flag: Bahrain',
+'flag flag bahrain bh nation country banner'
+],[
+'🇧🇮',
+'flag: Burundi',
+'flag flag burundi bi nation country banner'
+],[
+'🇧🇯',
+'flag: Benin',
+'flag flag benin bj nation country banner'
+],[
+'🇧🇱',
+'flag: St. Barthélemy',
+'flag flag st barthelemy saint barthélemy nation country banner'
+],[
+'🇧🇲',
+'flag: Bermuda',
+'flag flag bermuda bm nation country banner'
+],[
+'🇧🇳',
+'flag: Brunei',
+'flag flag brunei bn darussalam nation country banner'
+],[
+'🇧🇴',
+'flag: Bolivia',
+'flag flag bolivia bo nation country banner'
+],[
+'🇧🇶',
+'flag: Caribbean Netherlands',
+'flag flag caribbean netherlands bonaire nation country banner'
+],[
+'🇧🇷',
+'flag: Brazil',
+'flag flag brazil br nation country banner'
+],[
+'🇧🇸',
+'flag: Bahamas',
+'flag flag bahamas bs nation country banner'
+],[
+'🇧🇹',
+'flag: Bhutan',
+'flag flag bhutan bt nation country banner'
+],[
+'🇧🇻',
+'flag: Bouvet Island',
+'flag flag bouvet island norway'
+],[
+'🇧🇼',
+'flag: Botswana',
+'flag flag botswana bw nation country banner'
+],[
+'🇧🇾',
+'flag: Belarus',
+'flag flag belarus by nation country banner'
+],[
+'🇧🇿',
+'flag: Belize',
+'flag flag belize bz nation country banner'
+],[
+'🇨🇦',
+'flag: Canada',
+'flag flag canada ca nation country banner'
+],[
+'🇨🇨',
+'flag: Cocos (Keeling) Islands',
+'flag flag cocos islands cocos keeling islands nation country banner'
+],[
+'🇨🇩',
+'flag: Congo - Kinshasa',
+'flag flag congo kinshasa congo democratic republic nation country banner'
+],[
+'🇨🇫',
+'flag: Central African Republic',
+'flag flag central african republic central african republic nation country banner'
+],[
+'🇨🇬',
+'flag: Congo - Brazzaville',
+'flag flag congo brazzaville congo nation country banner'
+],[
+'🇨🇭',
+'flag: Switzerland',
+'flag flag switzerland ch nation country banner'
+],[
+'🇨🇮',
+'flag: Côte d’Ivoire',
+'flag flag cote d ivoire ivory coast nation country banner'
+],[
+'🇨🇰',
+'flag: Cook Islands',
+'flag flag cook islands cook islands nation country banner'
+],[
+'🇨🇱',
+'flag: Chile',
+'flag flag chile nation country banner'
+],[
+'🇨🇲',
+'flag: Cameroon',
+'flag flag cameroon cm nation country banner'
+],[
+'🇨🇳',
+'flag: China',
+'flag flag china china chinese prc country nation banner'
+],[
+'🇨🇴',
+'flag: Colombia',
+'flag flag colombia co nation country banner'
+],[
+'🇨🇵',
+'flag: Clipperton Island',
+'flag flag clipperton island'
+],[
+'🇨🇶',
+'flag: Sark',
+'flag flag sark'
+],[
+'🇨🇷',
+'flag: Costa Rica',
+'flag flag costa rica costa rica nation country banner'
+],[
+'🇨🇺',
+'flag: Cuba',
+'flag flag cuba cu nation country banner'
+],[
+'🇨🇻',
+'flag: Cape Verde',
+'flag flag cape verde cabo verde nation country banner'
+],[
+'🇨🇼',
+'flag: Curaçao',
+'flag flag curacao curaçao nation country banner'
+],[
+'🇨🇽',
+'flag: Christmas Island',
+'flag flag christmas island christmas island nation country banner'
+],[
+'🇨🇾',
+'flag: Cyprus',
+'flag flag cyprus cy nation country banner'
+],[
+'🇨🇿',
+'flag: Czechia',
+'flag flag czechia cz nation country banner'
+],[
+'🇩🇪',
+'flag: Germany',
+'flag flag germany german nation country banner'
+],[
+'🇩🇬',
+'flag: Diego Garcia',
+'flag flag diego garcia'
+],[
+'🇩🇯',
+'flag: Djibouti',
+'flag flag djibouti dj nation country banner'
+],[
+'🇩🇰',
+'flag: Denmark',
+'flag flag denmark dk nation country banner'
+],[
+'🇩🇲',
+'flag: Dominica',
+'flag flag dominica dm nation country banner'
+],[
+'🇩🇴',
+'flag: Dominican Republic',
+'flag flag dominican republic dominican republic nation country banner'
+],[
+'🇩🇿',
+'flag: Algeria',
+'flag flag algeria dz nation country banner'
+],[
+'🇪🇦',
+'flag: Ceuta & Melilla',
+'flag flag ceuta melilla'
+],[
+'🇪🇨',
+'flag: Ecuador',
+'flag flag ecuador ec nation country banner'
+],[
+'🇪🇪',
+'flag: Estonia',
+'flag flag estonia ee nation country banner'
+],[
+'🇪🇬',
+'flag: Egypt',
+'flag flag egypt eg nation country banner'
+],[
+'🇪🇭',
+'flag: Western Sahara',
+'flag flag western sahara western sahara nation country banner'
+],[
+'🇪🇷',
+'flag: Eritrea',
+'flag flag eritrea er nation country banner'
+],[
+'🇪🇸',
+'flag: Spain',
+'flag flag spain spain nation country banner'
+],[
+'🇪🇹',
+'flag: Ethiopia',
+'flag flag ethiopia et nation country banner'
+],[
+'🇪🇺',
+'flag: European Union',
+'flag flag european union european union banner'
+],[
+'🇫🇮',
+'flag: Finland',
+'flag flag finland fi nation country banner'
+],[
+'🇫🇯',
+'flag: Fiji',
+'flag flag fiji fj nation country banner'
+],[
+'🇫🇰',
+'flag: Falkland Islands',
+'flag flag falkland islands falkland islands malvinas nation country banner'
+],[
+'🇫🇲',
+'flag: Micronesia',
+'flag flag micronesia micronesia federated states nation country banner'
+],[
+'🇫🇴',
+'flag: Faroe Islands',
+'flag flag faroe islands faroe islands nation country banner'
+],[
+'🇫🇷',
+'flag: France',
+'flag flag france banner nation france french country'
+],[
+'🇬🇦',
+'flag: Gabon',
+'flag flag gabon ga nation country banner'
+],[
+'🇬🇧',
+'flag: United Kingdom',
+'flag flag united kingdom united kingdom great britain northern ireland nation country banner british UK english england union jack'
+],[
+'🇬🇩',
+'flag: Grenada',
+'flag flag grenada gd nation country banner'
+],[
+'🇬🇪',
+'flag: Georgia',
+'flag flag georgia ge nation country banner'
+],[
+'🇬🇫',
+'flag: French Guiana',
+'flag flag french guiana french guiana nation country banner'
+],[
+'🇬🇬',
+'flag: Guernsey',
+'flag flag guernsey gg nation country banner'
+],[
+'🇬🇭',
+'flag: Ghana',
+'flag flag ghana gh nation country banner'
+],[
+'🇬🇮',
+'flag: Gibraltar',
+'flag flag gibraltar gi nation country banner'
+],[
+'🇬🇱',
+'flag: Greenland',
+'flag flag greenland gl nation country banner'
+],[
+'🇬🇲',
+'flag: Gambia',
+'flag flag gambia gm nation country banner'
+],[
+'🇬🇳',
+'flag: Guinea',
+'flag flag guinea gn nation country banner'
+],[
+'🇬🇵',
+'flag: Guadeloupe',
+'flag flag guadeloupe gp nation country banner'
+],[
+'🇬🇶',
+'flag: Equatorial Guinea',
+'flag flag equatorial guinea equatorial gn nation country banner'
+],[
+'🇬🇷',
+'flag: Greece',
+'flag flag greece gr nation country banner'
+],[
+'🇬🇸',
+'flag: South Georgia & South Sandwich Islands',
+'flag flag south georgia south sandwich islands south georgia sandwich islands nation country banner'
+],[
+'🇬🇹',
+'flag: Guatemala',
+'flag flag guatemala gt nation country banner'
+],[
+'🇬🇺',
+'flag: Guam',
+'flag flag guam gu nation country banner'
+],[
+'🇬🇼',
+'flag: Guinea-Bissau',
+'flag flag guinea bissau gw bissau nation country banner'
+],[
+'🇬🇾',
+'flag: Guyana',
+'flag flag guyana gy nation country banner'
+],[
+'🇭🇰',
+'flag: Hong Kong SAR China',
+'flag flag hong kong sar china hong kong nation country banner'
+],[
+'🇭🇲',
+'flag: Heard & McDonald Islands',
+'flag flag heard mcdonald islands'
+],[
+'🇭🇳',
+'flag: Honduras',
+'flag flag honduras hn nation country banner'
+],[
+'🇭🇷',
+'flag: Croatia',
+'flag flag croatia hr nation country banner'
+],[
+'🇭🇹',
+'flag: Haiti',
+'flag flag haiti ht nation country banner'
+],[
+'🇭🇺',
+'flag: Hungary',
+'flag flag hungary hu nation country banner'
+],[
+'🇮🇨',
+'flag: Canary Islands',
+'flag flag canary islands canary islands nation country banner'
+],[
+'🇮🇩',
+'flag: Indonesia',
+'flag flag indonesia nation country banner'
+],[
+'🇮🇪',
+'flag: Ireland',
+'flag flag ireland ie nation country banner'
+],[
+'🇮🇱',
+'flag: Israel',
+'flag flag israel il nation country banner'
+],[
+'🇮🇲',
+'flag: Isle of Man',
+'flag flag isle of man isle man nation country banner'
+],[
+'🇮🇳',
+'flag: India',
+'flag flag india in nation country banner'
+],[
+'🇮🇴',
+'flag: British Indian Ocean Territory',
+'flag flag british indian ocean territory british indian ocean territory nation country banner'
+],[
+'🇮🇶',
+'flag: Iraq',
+'flag flag iraq iq nation country banner'
+],[
+'🇮🇷',
+'flag: Iran',
+'flag flag iran iran islamic republic nation country banner'
+],[
+'🇮🇸',
+'flag: Iceland',
+'flag flag iceland is nation country banner'
+],[
+'🇮🇹',
+'flag: Italy',
+'flag flag italy italy nation country banner'
+],[
+'🇯🇪',
+'flag: Jersey',
+'flag flag jersey je nation country banner'
+],[
+'🇯🇲',
+'flag: Jamaica',
+'flag flag jamaica jm nation country banner'
+],[
+'🇯🇴',
+'flag: Jordan',
+'flag flag jordan jo nation country banner'
+],[
+'🇯🇵',
+'flag: Japan',
+'flag flag japan japanese nation country banner'
+],[
+'🇰🇪',
+'flag: Kenya',
+'flag flag kenya ke nation country banner'
+],[
+'🇰🇬',
+'flag: Kyrgyzstan',
+'flag flag kyrgyzstan kg nation country banner'
+],[
+'🇰🇭',
+'flag: Cambodia',
+'flag flag cambodia kh nation country banner'
+],[
+'🇰🇮',
+'flag: Kiribati',
+'flag flag kiribati ki nation country banner'
+],[
+'🇰🇲',
+'flag: Comoros',
+'flag flag comoros km nation country banner'
+],[
+'🇰🇳',
+'flag: St. Kitts & Nevis',
+'flag flag st kitts nevis saint kitts nevis nation country banner'
+],[
+'🇰🇵',
+'flag: North Korea',
+'flag flag north korea north korea nation country banner'
+],[
+'🇰🇷',
+'flag: South Korea',
+'flag flag south korea south korea nation country banner'
+],[
+'🇰🇼',
+'flag: Kuwait',
+'flag flag kuwait kw nation country banner'
+],[
+'🇰🇾',
+'flag: Cayman Islands',
+'flag flag cayman islands cayman islands nation country banner'
+],[
+'🇰🇿',
+'flag: Kazakhstan',
+'flag flag kazakhstan kz nation country banner'
+],[
+'🇱🇦',
+'flag: Laos',
+'flag flag laos lao democratic republic nation country banner'
+],[
+'🇱🇧',
+'flag: Lebanon',
+'flag flag lebanon lb nation country banner'
+],[
+'🇱🇨',
+'flag: St. Lucia',
+'flag flag st lucia saint lucia nation country banner'
+],[
+'🇱🇮',
+'flag: Liechtenstein',
+'flag flag liechtenstein li nation country banner'
+],[
+'🇱🇰',
+'flag: Sri Lanka',
+'flag flag sri lanka sri lanka nation country banner'
+],[
+'🇱🇷',
+'flag: Liberia',
+'flag flag liberia lr nation country banner'
+],[
+'🇱🇸',
+'flag: Lesotho',
+'flag flag lesotho ls nation country banner'
+],[
+'🇱🇹',
+'flag: Lithuania',
+'flag flag lithuania lt nation country banner'
+],[
+'🇱🇺',
+'flag: Luxembourg',
+'flag flag luxembourg lu nation country banner'
+],[
+'🇱🇻',
+'flag: Latvia',
+'flag flag latvia lv nation country banner'
+],[
+'🇱🇾',
+'flag: Libya',
+'flag flag libya ly nation country banner'
+],[
+'🇲🇦',
+'flag: Morocco',
+'flag flag morocco ma nation country banner'
+],[
+'🇲🇨',
+'flag: Monaco',
+'flag flag monaco mc nation country banner'
+],[
+'🇲🇩',
+'flag: Moldova',
+'flag flag moldova moldova republic nation country banner'
+],[
+'🇲🇪',
+'flag: Montenegro',
+'flag flag montenegro me nation country banner'
+],[
+'🇲🇫',
+'flag: St. Martin',
+'flag flag st martin'
+],[
+'🇲🇬',
+'flag: Madagascar',
+'flag flag madagascar mg nation country banner'
+],[
+'🇲🇭',
+'flag: Marshall Islands',
+'flag flag marshall islands marshall islands nation country banner'
+],[
+'🇲🇰',
+'flag: North Macedonia',
+'flag flag north macedonia macedonia nation country banner'
+],[
+'🇲🇱',
+'flag: Mali',
+'flag flag mali ml nation country banner'
+],[
+'🇲🇲',
+'flag: Myanmar (Burma)',
+'flag flag myanmar mm nation country banner'
+],[
+'🇲🇳',
+'flag: Mongolia',
+'flag flag mongolia mn nation country banner'
+],[
+'🇲🇴',
+'flag: Macao SAR China',
+'flag flag macao sar china macao nation country banner'
+],[
+'🇲🇵',
+'flag: Northern Mariana Islands',
+'flag flag northern mariana islands northern mariana islands nation country banner'
+],[
+'🇲🇶',
+'flag: Martinique',
+'flag flag martinique mq nation country banner'
+],[
+'🇲🇷',
+'flag: Mauritania',
+'flag flag mauritania mr nation country banner'
+],[
+'🇲🇸',
+'flag: Montserrat',
+'flag flag montserrat ms nation country banner'
+],[
+'🇲🇹',
+'flag: Malta',
+'flag flag malta mt nation country banner'
+],[
+'🇲🇺',
+'flag: Mauritius',
+'flag flag mauritius mu nation country banner'
+],[
+'🇲🇻',
+'flag: Maldives',
+'flag flag maldives mv nation country banner'
+],[
+'🇲🇼',
+'flag: Malawi',
+'flag flag malawi mw nation country banner'
+],[
+'🇲🇽',
+'flag: Mexico',
+'flag flag mexico mx nation country banner'
+],[
+'🇲🇾',
+'flag: Malaysia',
+'flag flag malaysia my nation country banner'
+],[
+'🇲🇿',
+'flag: Mozambique',
+'flag flag mozambique mz nation country banner'
+],[
+'🇳🇦',
+'flag: Namibia',
+'flag flag namibia na nation country banner'
+],[
+'🇳🇨',
+'flag: New Caledonia',
+'flag flag new caledonia new caledonia nation country banner'
+],[
+'🇳🇪',
+'flag: Niger',
+'flag flag niger ne nation country banner'
+],[
+'🇳🇫',
+'flag: Norfolk Island',
+'flag flag norfolk island norfolk island nation country banner'
+],[
+'🇳🇬',
+'flag: Nigeria',
+'flag flag nigeria nation country banner'
+],[
+'🇳🇮',
+'flag: Nicaragua',
+'flag flag nicaragua ni nation country banner'
+],[
+'🇳🇱',
+'flag: Netherlands',
+'flag flag netherlands nl nation country banner'
+],[
+'🇳🇴',
+'flag: Norway',
+'flag flag norway no nation country banner'
+],[
+'🇳🇵',
+'flag: Nepal',
+'flag flag nepal np nation country banner'
+],[
+'🇳🇷',
+'flag: Nauru',
+'flag flag nauru nr nation country banner'
+],[
+'🇳🇺',
+'flag: Niue',
+'flag flag niue nu nation country banner'
+],[
+'🇳🇿',
+'flag: New Zealand',
+'flag flag new zealand new zealand nation country banner'
+],[
+'🇴🇲',
+'flag: Oman',
+'flag flag oman om symbol nation country banner'
+],[
+'🇵🇦',
+'flag: Panama',
+'flag flag panama pa nation country banner'
+],[
+'🇵🇪',
+'flag: Peru',
+'flag flag peru pe nation country banner'
+],[
+'🇵🇫',
+'flag: French Polynesia',
+'flag flag french polynesia french polynesia nation country banner'
+],[
+'🇵🇬',
+'flag: Papua New Guinea',
+'flag flag papua new guinea papua new guinea nation country banner'
+],[
+'🇵🇭',
+'flag: Philippines',
+'flag flag philippines ph nation country banner'
+],[
+'🇵🇰',
+'flag: Pakistan',
+'flag flag pakistan pk nation country banner'
+],[
+'🇵🇱',
+'flag: Poland',
+'flag flag poland pl nation country banner'
+],[
+'🇵🇲',
+'flag: St. Pierre & Miquelon',
+'flag flag st pierre miquelon saint pierre miquelon nation country banner'
+],[
+'🇵🇳',
+'flag: Pitcairn Islands',
+'flag flag pitcairn islands pitcairn nation country banner'
+],[
+'🇵🇷',
+'flag: Puerto Rico',
+'flag flag puerto rico puerto rico nation country banner'
+],[
+'🇵🇸',
+'flag: Palestinian Territories',
+'flag flag palestinian territories palestine palestinian territories nation country banner'
+],[
+'🇵🇹',
+'flag: Portugal',
+'flag flag portugal pt nation country banner'
+],[
+'🇵🇼',
+'flag: Palau',
+'flag flag palau pw nation country banner'
+],[
+'🇵🇾',
+'flag: Paraguay',
+'flag flag paraguay py nation country banner'
+],[
+'🇶🇦',
+'flag: Qatar',
+'flag flag qatar qa nation country banner'
+],[
+'🇷🇪',
+'flag: Réunion',
+'flag flag reunion réunion nation country banner'
+],[
+'🇷🇴',
+'flag: Romania',
+'flag flag romania ro nation country banner'
+],[
+'🇷🇸',
+'flag: Serbia',
+'flag flag serbia rs nation country banner'
+],[
+'🇷🇺',
+'flag: Russia',
+'flag flag russia russian federation nation country banner'
+],[
+'🇷🇼',
+'flag: Rwanda',
+'flag flag rwanda rw nation country banner'
+],[
+'🇸🇦',
+'flag: Saudi Arabia',
+'flag flag saudi arabia nation country banner'
+],[
+'🇸🇧',
+'flag: Solomon Islands',
+'flag flag solomon islands solomon islands nation country banner'
+],[
+'🇸🇨',
+'flag: Seychelles',
+'flag flag seychelles sc nation country banner'
+],[
+'🇸🇩',
+'flag: Sudan',
+'flag flag sudan sd nation country banner'
+],[
+'🇸🇪',
+'flag: Sweden',
+'flag flag sweden se nation country banner'
+],[
+'🇸🇬',
+'flag: Singapore',
+'flag flag singapore sg nation country banner'
+],[
+'🇸🇭',
+'flag: St. Helena',
+'flag flag st helena saint helena ascension tristan cunha nation country banner'
+],[
+'🇸🇮',
+'flag: Slovenia',
+'flag flag slovenia si nation country banner'
+],[
+'🇸🇯',
+'flag: Svalbard & Jan Mayen',
+'flag flag svalbard jan mayen'
+],[
+'🇸🇰',
+'flag: Slovakia',
+'flag flag slovakia sk nation country banner'
+],[
+'🇸🇱',
+'flag: Sierra Leone',
+'flag flag sierra leone sierra leone nation country banner'
+],[
+'🇸🇲',
+'flag: San Marino',
+'flag flag san marino san marino nation country banner'
+],[
+'🇸🇳',
+'flag: Senegal',
+'flag flag senegal sn nation country banner'
+],[
+'🇸🇴',
+'flag: Somalia',
+'flag flag somalia so nation country banner'
+],[
+'🇸🇷',
+'flag: Suriname',
+'flag flag suriname sr nation country banner'
+],[
+'🇸🇸',
+'flag: South Sudan',
+'flag flag south sudan south sd nation country banner'
+],[
+'🇸🇹',
+'flag: São Tomé & Príncipe',
+'flag flag sao tome principe sao tome principe nation country banner'
+],[
+'🇸🇻',
+'flag: El Salvador',
+'flag flag el salvador el salvador nation country banner'
+],[
+'🇸🇽',
+'flag: Sint Maarten',
+'flag flag sint maarten sint maarten dutch nation country banner'
+],[
+'🇸🇾',
+'flag: Syria',
+'flag flag syria syrian arab republic nation country banner'
+],[
+'🇸🇿',
+'flag: Eswatini',
+'flag flag eswatini sz nation country banner'
+],[
+'🇹🇦',
+'flag: Tristan da Cunha',
+'flag flag tristan da cunha'
+],[
+'🇹🇨',
+'flag: Turks & Caicos Islands',
+'flag flag turks caicos islands turks caicos islands nation country banner'
+],[
+'🇹🇩',
+'flag: Chad',
+'flag flag chad td nation country banner'
+],[
+'🇹🇫',
+'flag: French Southern Territories',
+'flag flag french southern territories french southern territories nation country banner'
+],[
+'🇹🇬',
+'flag: Togo',
+'flag flag togo tg nation country banner'
+],[
+'🇹🇭',
+'flag: Thailand',
+'flag flag thailand th nation country banner'
+],[
+'🇹🇯',
+'flag: Tajikistan',
+'flag flag tajikistan tj nation country banner'
+],[
+'🇹🇰',
+'flag: Tokelau',
+'flag flag tokelau tk nation country banner'
+],[
+'🇹🇱',
+'flag: Timor-Leste',
+'flag flag timor leste timor leste nation country banner'
+],[
+'🇹🇲',
+'flag: Turkmenistan',
+'flag flag turkmenistan nation country banner'
+],[
+'🇹🇳',
+'flag: Tunisia',
+'flag flag tunisia tn nation country banner'
+],[
+'🇹🇴',
+'flag: Tonga',
+'flag flag tonga to nation country banner'
+],[
+'🇹🇷',
+'flag: Turkey',
+'flag flag turkey turkey nation country banner'
+],[
+'🇹🇹',
+'flag: Trinidad & Tobago',
+'flag flag trinidad tobago trinidad tobago nation country banner'
+],[
+'🇹🇻',
+'flag: Tuvalu',
+'flag flag tuvalu nation country banner'
+],[
+'🇹🇼',
+'flag: Taiwan',
+'flag flag taiwan tw nation country banner'
+],[
+'🇹🇿',
+'flag: Tanzania',
+'flag flag tanzania tanzania united republic nation country banner'
+],[
+'🇺🇦',
+'flag: Ukraine',
+'flag flag ukraine ua nation country banner'
+],[
+'🇺🇬',
+'flag: Uganda',
+'flag flag uganda ug nation country banner'
+],[
+'🇺🇲',
+'flag: U.S. Outlying Islands',
+'flag flag u s outlying islands'
+],[
+'🇺🇳',
+'flag: United Nations',
+'flag flag united nations un banner'
+],[
+'🇺🇸',
+'flag: United States',
+'flag flag united states united states america nation country banner'
+],[
+'🇺🇾',
+'flag: Uruguay',
+'flag flag uruguay uy nation country banner'
+],[
+'🇺🇿',
+'flag: Uzbekistan',
+'flag flag uzbekistan uz nation country banner'
+],[
+'🇻🇦',
+'flag: Vatican City',
+'flag flag vatican city vatican city nation country banner'
+],[
+'🇻🇨',
+'flag: St. Vincent & Grenadines',
+'flag flag st vincent grenadines saint vincent grenadines nation country banner'
+],[
+'🇻🇪',
+'flag: Venezuela',
+'flag flag venezuela ve bolivarian republic nation country banner'
+],[
+'🇻🇬',
+'flag: British Virgin Islands',
+'flag flag british virgin islands british virgin islands bvi nation country banner'
+],[
+'🇻🇮',
+'flag: U.S. Virgin Islands',
+'flag flag u s virgin islands virgin islands us nation country banner'
+],[
+'🇻🇳',
+'flag: Vietnam',
+'flag flag vietnam viet nam nation country banner'
+],[
+'🇻🇺',
+'flag: Vanuatu',
+'flag flag vanuatu vu nation country banner'
+],[
+'🇼🇫',
+'flag: Wallis & Futuna',
+'flag flag wallis futuna wallis futuna nation country banner'
+],[
+'🇼🇸',
+'flag: Samoa',
+'flag flag samoa ws nation country banner'
+],[
+'🇽🇰',
+'flag: Kosovo',
+'flag flag kosovo xk nation country banner'
+],[
+'🇾🇪',
+'flag: Yemen',
+'flag flag yemen ye nation country banner'
+],[
+'🇾🇹',
+'flag: Mayotte',
+'flag flag mayotte yt nation country banner'
+],[
+'🇿🇦',
+'flag: South Africa',
+'flag flag south africa south africa nation country banner'
+],[
+'🇿🇲',
+'flag: Zambia',
+'flag flag zambia zm nation country banner'
+],[
+'🇿🇼',
+'flag: Zimbabwe',
+'flag flag zimbabwe zw nation country banner'
+],[
+'🏴',
+'flag: England',
+'flag flag england english'
+],[
+'🏴',
+'flag: Scotland',
+'flag flag scotland scottish'
+],[
+'🏴',
+'flag: Wales',
+'flag flag wales welsh'
+]
+];
+
+//this list is taken from https://unicode.org/emoji/charts/full-emoji-modifiers.html
+//Full Emoji Modifier Sequences, v15.1
+
+const MODED = [
+'👋🏻',
+'🤚🏻',
+'🖐🏻',
+'✋🏻',
+'🖖🏻',
+'🫱🏻',
+'🫲🏻',
+'🫳🏻',
+'🫴🏻',
+'🫷🏻',
+'🫸🏻',
+'👌🏻',
+'🤌🏻',
+'🤏🏻',
+'✌🏻',
+'🤞🏻',
+'🫰🏻',
+'🤟🏻',
+'🤘🏻',
+'🤙🏻',
+'👈🏻',
+'👉🏻',
+'👆🏻',
+'🖕🏻',
+'👇🏻',
+'☝🏻',
+'🫵🏻',
+'👍🏻',
+'👎🏻',
+'✊🏻',
+'👊🏻',
+'🤛🏻',
+'🤜🏻',
+'👏🏻',
+'🙌🏻',
+'🫶🏻',
+'👐🏻',
+'🤲🏻',
+'🤝🏻',
+'🙏🏻',
+'✍🏻',
+'💅🏻',
+'🤳🏻',
+'💪🏻',
+'🦵🏻',
+'🦶🏻',
+'👂🏻',
+'🦻🏻',
+'👃🏻',
+'👶🏻',
+'🧒🏻',
+'👦🏻',
+'👧🏻',
+'🧑🏻',
+'👱🏻',
+'👨🏻',
+'🧔🏻',
+'🧔🏻♂️',
+'🧔🏻♀️',
+'👨🏻🦰',
+'👨🏻🦱',
+'👨🏻🦳',
+'👨🏻🦲',
+'👩🏻',
+'👩🏻🦰',
+'🧑🏻🦰',
+'👩🏻🦱',
+'🧑🏻🦱',
+'👩🏻🦳',
+'🧑🏻🦳',
+'👩🏻🦲',
+'🧑🏻🦲',
+'👱🏻♀️',
+'👱🏻♂️',
+'🧓🏻',
+'👴🏻',
+'👵🏻',
+'🙍🏻',
+'🙍🏻♂️',
+'🙍🏻♀️',
+'🙎🏻',
+'🙎🏻♂️',
+'🙎🏻♀️',
+'🙅🏻',
+'🙅🏻♂️',
+'🙅🏻♀️',
+'🙆🏻',
+'🙆🏻♂️',
+'🙆🏻♀️',
+'💁🏻',
+'💁🏻♂️',
+'💁🏻♀️',
+'🙋🏻',
+'🙋🏻♂️',
+'🙋🏻♀️',
+'🧏🏻',
+'🧏🏻♂️',
+'🧏🏻♀️',
+'🙇🏻',
+'🙇🏻♂️',
+'🙇🏻♀️',
+'🤦🏻',
+'🤦🏻♂️',
+'🤦🏻♀️',
+'🤷🏻',
+'🤷🏻♂️',
+'🤷🏻♀️',
+'🧑🏻⚕️',
+'👨🏻⚕️',
+'👩🏻⚕️',
+'🧑🏻🎓',
+'👨🏻🎓',
+'👩🏻🎓',
+'🧑🏻🏫',
+'👨🏻🏫',
+'👩🏻🏫',
+'🧑🏻⚖️',
+'👨🏻⚖️',
+'👩🏻⚖️',
+'🧑🏻🌾',
+'👨🏻🌾',
+'👩🏻🌾',
+'🧑🏻🍳',
+'👨🏻🍳',
+'👩🏻🍳',
+'🧑🏻🔧',
+'👨🏻🔧',
+'👩🏻🔧',
+'🧑🏻🏭',
+'👨🏻🏭',
+'👩🏻🏭',
+'🧑🏻💼',
+'👨🏻💼',
+'👩🏻💼',
+'🧑🏻🔬',
+'👨🏻🔬',
+'👩🏻🔬',
+'🧑🏻💻',
+'👨🏻💻',
+'👩🏻💻',
+'🧑🏻🎤',
+'👨🏻🎤',
+'👩🏻🎤',
+'🧑🏻🎨',
+'👨🏻🎨',
+'👩🏻🎨',
+'🧑🏻✈️',
+'👨🏻✈️',
+'👩🏻✈️',
+'🧑🏻🚀',
+'👨🏻🚀',
+'👩🏻🚀',
+'🧑🏻🚒',
+'👨🏻🚒',
+'👩🏻🚒',
+'👮🏻',
+'👮🏻♂️',
+'👮🏻♀️',
+'🕵🏻',
+'🕵🏻♂️',
+'🕵🏻♀️',
+'💂🏻',
+'💂🏻♂️',
+'💂🏻♀️',
+'🥷🏻',
+'👷🏻',
+'👷🏻♂️',
+'👷🏻♀️',
+'🫅🏻',
+'🤴🏻',
+'👸🏻',
+'👳🏻',
+'👳🏻♂️',
+'👳🏻♀️',
+'👲🏻',
+'🧕🏻',
+'🤵🏻',
+'🤵🏻♂️',
+'🤵🏻♀️',
+'👰🏻',
+'👰🏻♂️',
+'👰🏻♀️',
+'🤰🏻',
+'🫃🏻',
+'🫄🏻',
+'🤱🏻',
+'👩🏻🍼',
+'👨🏻🍼',
+'🧑🏻🍼',
+'👼🏻',
+'🎅🏻',
+'🤶🏻',
+'🧑🏻🎄',
+'🦸🏻',
+'🦸🏻♂️',
+'🦸🏻♀️',
+'🦹🏻',
+'🦹🏻♂️',
+'🦹🏻♀️',
+'🧙🏻',
+'🧙🏻♂️',
+'🧙🏻♀️',
+'🧚🏻',
+'🧚🏻♂️',
+'🧚🏻♀️',
+'🧛🏻',
+'🧛🏻♂️',
+'🧛🏻♀️',
+'🧜🏻',
+'🧜🏻♂️',
+'🧜🏻♀️',
+'🧝🏻',
+'🧝🏻♂️',
+'🧝🏻♀️',
+'💆🏻',
+'💆🏻♂️',
+'💆🏻♀️',
+'💇🏻',
+'💇🏻♂️',
+'💇🏻♀️',
+'🚶🏻',
+'🚶🏻♂️',
+'🚶🏻♀️',
+'🚶🏻➡️',
+'🚶🏻♀️➡️',
+'🚶🏻♂️➡️',
+'🧍🏻',
+'🧍🏻♂️',
+'🧍🏻♀️',
+'🧎🏻',
+'🧎🏻♂️',
+'🧎🏻♀️',
+'🧎🏻➡️',
+'🧎🏻♀️➡️',
+'🧎🏻♂️➡️',
+'🧑🏻🦯',
+'🧑🏻🦯➡️',
+'👨🏻🦯',
+'👨🏻🦯➡️',
+'👩🏻🦯',
+'👩🏻🦯➡️',
+'🧑🏻🦼',
+'🧑🏻🦼➡️',
+'👨🏻🦼',
+'👨🏻🦼➡️',
+'👩🏻🦼',
+'👩🏻🦼➡️',
+'🧑🏻🦽',
+'🧑🏻🦽➡️',
+'👨🏻🦽',
+'👨🏻🦽➡️',
+'👩🏻🦽',
+'👩🏻🦽➡️',
+'🏃🏻',
+'🏃🏻♂️',
+'🏃🏻♀️',
+'🏃🏻➡️',
+'🏃🏻♀️➡️',
+'🏃🏻♂️➡️',
+'💃🏻',
+'🕺🏻',
+'🕴🏻',
+'🧖🏻',
+'🧖🏻♂️',
+'🧖🏻♀️',
+'🧗🏻',
+'🧗🏻♂️',
+'🧗🏻♀️',
+'🏇🏻',
+'🏂🏻',
+'🏌🏻',
+'🏌🏻♂️',
+'🏌🏻♀️',
+'🏄🏻',
+'🏄🏻♂️',
+'🏄🏻♀️',
+'🚣🏻',
+'🚣🏻♂️',
+'🚣🏻♀️',
+'🏊🏻',
+'🏊🏻♂️',
+'🏊🏻♀️',
+'⛹🏻',
+'⛹🏻♂️',
+'⛹🏻♀️',
+'🏋🏻',
+'🏋🏻♂️',
+'🏋🏻♀️',
+'🚴🏻',
+'🚴🏻♂️',
+'🚴🏻♀️',
+'🚵🏻',
+'🚵🏻♂️',
+'🚵🏻♀️',
+'🤸🏻',
+'🤸🏻♂️',
+'🤸🏻♀️',
+'🤽🏻',
+'🤽🏻♂️',
+'🤽🏻♀️',
+'🤾🏻',
+'🤾🏻♂️',
+'🤾🏻♀️',
+'🤹🏻',
+'🤹🏻♂️',
+'🤹🏻♀️',
+'🧘🏻',
+'🧘🏻♂️',
+'🧘🏻♀️',
+'🛀🏻',
+'🛌🏻',
+'🧑🏻🤝🧑🏻',
+'👭🏻',
+'👫🏻',
+'👬🏻',
+'💏🏻',
+'👩🏻❤️💋👨🏻',
+'👨🏻❤️💋👨🏻',
+'👩🏻❤️💋👩🏻',
+'💑🏻',
+'👩🏻❤️👨🏻',
+'👨🏻❤️👨🏻',
+'👩🏻❤️👩🏻'
+
+];
+
+// MODED is the emojis with the skin tone modifier \u{1F3FB} included
+// create MODABLE[], the same emoji but with the skin tone modifiers removed.
+const MODABLE = [];
+for (let i = 0; i < MODED.length; i++) {
+ MODABLE[i] = MODED[i].replace(/\u{1F3FB}/ug, '');
+}
+
+module.exports = {EMOJI, MODED, MODABLE};
diff --git a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/metadata.json b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/metadata.json
new file mode 100644
index 0000000000..f830fa725e
--- /dev/null
+++ b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/metadata.json
@@ -0,0 +1,7 @@
+{
+ "uuid": "icon-menu@cinnamon.org",
+ "name": "Icon Menu",
+ "description": "A grid layout applications menu.",
+ "icon": "applications-other",
+ "max-instances": -1
+}
diff --git a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/settings-schema.json b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/settings-schema.json
new file mode 100755
index 0000000000..a0d0d37d9f
--- /dev/null
+++ b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/settings-schema.json
@@ -0,0 +1,369 @@
+{
+ "layout":{
+ "type":"layout",
+ "pages":[
+ "layout",
+ "behaviour",
+ "search",
+ "appearance"
+ ],
+ "layout":{
+ "type":"page",
+ "title":"Layout",
+ "sections":[
+ "menu-layout",
+ "menu-content"
+ ]
+ },
+ "behaviour":{
+ "type":"page",
+ "title":"Behavior",
+ "sections":[
+ "panel-behave",
+ "menu-behave"
+ ]
+ },
+ "search":{
+ "type":"page",
+ "title":"Search",
+ "sections":[
+ "search-search"
+ ]
+ },
+ "appearance":{
+ "type":"page",
+ "title":"Appearance",
+ "sections":[
+ "panel-appear",
+ "menu-icons"
+ ]
+ },
+ "menu-layout":{
+ "type":"section",
+ "title":"Layout",
+ "keys":[
+ "show-sidebar",
+ "sidebar-placement",
+ "sidebar-favorites",
+ "description-placement"
+ ]
+ },
+ "menu-content":{
+ "type":"section",
+ "title":"Content",
+ "keys":[
+ "show-categories",
+ "show-places-category",
+ "show-recents-category",
+ "show-favorite-apps-category",
+ "show-home-folder-category",
+ "menu-editor-button"
+ ]
+ },
+ "panel-behave":{
+ "type":"section",
+ "title":"Panel",
+ "keys":[
+ "overlay-key",
+ "activate-on-hover",
+ "hover-delay",
+ "enable-animation"
+ ]
+ },
+ "menu-behave":{
+ "type":"section",
+ "title":"Menu",
+ "keys":[
+ "category-click",
+ "open-on-category",
+ "enable-autoscroll",
+ "show-hidden-files"
+ ]
+ },
+ "search-search":{
+ "type":"section",
+ "title":"Search",
+ "keys":[
+ "enable-emoji-search",
+ "enable-home-folder-search",
+ "search-help"
+ ]
+ },
+ "panel-appear":{
+ "type":"section",
+ "title":"Panel",
+ "keys":[
+ "menu-icon-custom",
+ "menu-icon",
+ "menu-icon-size-custom",
+ "menu-icon-size",
+ "menu-label"
+ ]
+ },
+ "menu-icons":{
+ "type":"section",
+ "title":"Menu",
+ "keys":[
+ "category-icon-size",
+ "apps-grid-icon-size",
+ "sidebar-icon-size"
+ ]
+ }
+ },
+
+ "categories":{
+ "type":"generic",
+ "default":[]
+ },
+ "custom-menu-width":{
+ "type":"generic",
+ "default":800
+ },
+ "custom-menu-height":{
+ "type":"generic",
+ "default":530
+ },
+ "recent-apps":{
+ "type":"generic",
+ "default":[]
+ },
+ "folder-categories":{
+ "type":"generic",
+ "default":[]
+ },
+
+ "show-sidebar":{
+ "type":"switch",
+ "default":true,
+ "description":"Show sidebar",
+ "tooltip":"Show the sidebar in the menu"
+ },
+ "sidebar-placement":{
+ "dependency":"show-sidebar",
+ "type":"combobox",
+ "default":1,
+ "description":"Sidebar location",
+ "options":{
+ "Top":0,
+ "Bottom":1,
+ "Left":2,
+ "Right":3
+ },
+ "tooltip":"Choose where to show the sidebar"
+ },
+ "sidebar-favorites":{
+ "dependency":"show-sidebar",
+ "type":"combobox",
+ "default":3,
+ "description":"Show favorites on sidebar",
+ "options":{
+ "None":0,
+ "Apps only":1,
+ "Files only":2,
+ "Apps and files":3
+ },
+ "tooltip":"Show your favorite apps and file icons on the sidebar"
+ },
+ "description-placement":{
+ "type":"combobox",
+ "default":0,
+ "description":"Application description placement",
+ "options":{
+ "Tooltips":0,
+ "Under titles":1,
+ "None":2
+ },
+ "tooltip":"Choose where to show application descriptions"
+ },
+
+ "show-categories":{
+ "type":"switch",
+ "default":true,
+ "description":"Show categories",
+ "tooltip":"Show list of application categories"
+ },
+ "show-places-category":{
+ "dependency":"show-categories",
+ "type":"switch",
+ "default":true,
+ "description":"Show bookmarks and places",
+ "tooltip":"Show bookmarks and places category in the menu"
+ },
+ "show-recents-category":{
+ "dependency":"show-categories",
+ "type":"switch",
+ "default":true,
+ "description":"Show recent items",
+ "tooltip":"Show recent items category in the menu"
+ },
+ "show-favorite-apps-category":{
+ "dependency":"show-categories",
+ "type":"switch",
+ "default":false,
+ "description":"Show favorite apps category",
+ "tooltip":"Show your favorite apps category in the menu"
+ },
+ "show-home-folder-category":{
+ "dependency":"show-categories",
+ "type":"checkbox",
+ "default":true,
+ "description":"Show home folder",
+ "tooltip":"Show home folder category in the menu"
+ },
+ "menu-editor-button":{
+ "type":"button",
+ "description":"Open the menu editor",
+ "callback":"launchEditor"
+ },
+
+ "overlay-key":{
+ "type":"keybinding",
+ "description":"Keyboard shortcut to open and close the menu",
+ "default":"Super_L::Super_R"
+ },
+ "activate-on-hover":{
+ "type":"checkbox",
+ "default":false,
+ "description":"Open menu on hover",
+ "tooltip":"Open the menu when I move my mouse over the panel icon"
+ },
+ "hover-delay":{
+ "dependency":"activate-on-hover",
+ "type":"spinbutton",
+ "default":50,
+ "min":0,
+ "max":1000,
+ "step":50,
+ "units":"milliseconds",
+ "description":"Menu hover delay"
+ },
+ "enable-animation":{
+ "type":"checkbox",
+ "default":false,
+ "description":"Use menu animations",
+ "tooltip":"Animate the menu on open and close"
+ },
+
+ "category-click":{
+ "dependency":"show-categories",
+ "type":"checkbox",
+ "default":true,
+ "description":"Activate categories on click",
+ "tooltip":"Activate categories on click instead of on hover"
+ },
+ "open-on-category":{
+ "dependency":"show-categories",
+ "type":"combobox",
+ "default":0,
+ "description":"Open menu on category:",
+ "tooltip":"Always open the menu with this category selected",
+ "options":{
+ "Last used category":0,
+ "Favorite apps":1,
+ "Recent":2,
+ "Places":3,
+ "All applications":4
+ }
+ },
+ "enable-autoscroll":{
+ "type":"checkbox",
+ "default":true,
+ "description":"Enable autoscrolling",
+ "tooltip":"Autoscroll the application list on mouse hover"
+ },
+ "show-hidden-files":{
+ "dependency":"show-categories",
+ "type":"checkbox",
+ "default":false,
+ "description":"Show hidden files",
+ "tooltip":"Show hidden files in folder view"
+ },
+
+ "enable-emoji-search":{
+ "type":"checkbox",
+ "default":false,
+ "description":"Emoji search",
+ "tooltip":"Show emojis in search results"
+ },
+ "enable-home-folder-search":{
+ "type":"checkbox",
+ "default":false,
+ "description":"Search home folder",
+ "tooltip":"When searching, also search my home folder for files"
+ },
+ "search-help":{
+ "type":"label",
+ "description":"\nNote: Search options can still be used if you have not enabled them here by prefixing your search term.\nFor example: to search for an emoji, type 'e heart' and use prefix 'f ' for file search.\n"
+ },
+
+ "menu-icon-custom":{
+ "type":"checkbox",
+ "default":false,
+ "description":"Use a custom icon",
+ "tooltip":"Use a custom icon in the panel"
+ },
+ "menu-icon":{
+ "type":"iconfilechooser",
+ "default":"cinnamon-symbolic",
+ "description":"Panel icon",
+ "dependency":"menu-icon-custom",
+ "default_icon" : "cinnamon-symbolic",
+ "tooltip":"Select an icon to show in the panel."
+ },
+ "menu-icon-size-custom":{
+ "type":"checkbox",
+ "default":false,
+ "description":"Use a custom icon size",
+ "tooltip":"Use a custom size for the panel icon"
+ },
+ "menu-icon-size":{
+ "type":"spinbutton",
+ "default":24,
+ "min":0,
+ "max":60,
+ "step":1,
+ "units":"",
+ "description":"Panel icon size (pixels)",
+ "dependency":"menu-icon-size-custom",
+ "tooltip":"Choose a custom size for the panel icon"
+ },
+ "menu-label":{
+ "type":"entry",
+ "default":"Menu",
+ "description":"Panel text",
+ "tooltip":"Text to show beside the panel icon"
+ },
+
+ "category-icon-size":{
+ "dependency":"show-categories",
+ "type":"spinbutton",
+ "default":24,
+ "min":0,
+ "max":64,
+ "step":1,
+ "units":"",
+ "description":"Category icon size (pixels)",
+ "tooltip":"Category icon size (pixels)"
+ },
+ "apps-grid-icon-size":{
+ "type":"spinbutton",
+ "default":64,
+ "min":0,
+ "max":128,
+ "step":1,
+ "units":"",
+ "description":"Applications grid icon size (pixels)",
+ "tooltip":"Applications grid icon size (pixels)"
+ },
+ "sidebar-icon-size":{
+ "dependency":"show-sidebar",
+ "type":"spinbutton",
+ "default":32,
+ "min":6,
+ "max":64,
+ "step":1,
+ "units":"",
+ "description":"Sidebar icon size (pixels)",
+ "tooltip":"Sidebar icon size (pixels)"
+ }
+}
diff --git a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/sidebar.js b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/sidebar.js
new file mode 100644
index 0000000000..1d0ac61475
--- /dev/null
+++ b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/sidebar.js
@@ -0,0 +1,416 @@
+const Gio = imports.gi.Gio;
+const Gtk = imports.gi.Gtk;
+const Atk = imports.gi.Atk;
+const Clutter = imports.gi.Clutter;
+const Util = imports.misc.util;
+const St = imports.gi.St;
+const Main = imports.ui.main;
+const {SignalManager} = imports.misc.signalManager;
+const {DragMotionResult, makeDraggable} = imports.ui.dnd;
+
+const {
+ wordWrap,
+ getThumbnail_gicon,
+ showTooltip,
+ hideTooltipIfVisible,
+ scrollToButton
+} = require('./utils');
+const SidebarPlacement = Object.freeze({ TOP: 0, BOTTOM: 1, LEFT: 2, RIGHT: 3});
+const DescriptionPlacement = Object.freeze({TOOLTIP: 0, UNDER: 1, NONE: 2});
+
+class SidebarButton {
+ constructor(appThis, icon, app, name, description, callback) {
+ //super({ hover: false, activate: false });
+ this.appThis = appThis;
+ this.signals = new SignalManager(null);
+ this.app = app;
+ this.name = name;
+ this.description = description;
+ this.callback = callback;
+ this.actor = new St.BoxLayout({
+ style_class: 'menu-favorites-button',
+ reactive: true,
+ accessible_role: Atk.Role.MENU_ITEM
+ });
+
+ this.has_focus = false;
+ if (icon) {
+ this.icon = icon;
+ this.actor.add_actor(this.icon);
+ }
+
+ //----------dnd--------------
+ this.actor._delegate = { //make all sidebar items a drag target for apps and files
+ handleDragOver: (source) => {
+ if (source.isDraggableApp) {
+ if (this.app && this.app.isApplication && source.id !== this.app.id) {
+ this.actor.set_opacity(40);
+ }
+ return DragMotionResult.MOVE_DROP;
+ } else if (source.isDraggableFile) {
+ return DragMotionResult.MOVE_DROP;
+ }
+ return DragMotionResult.NO_DROP;
+ },
+ handleDragOut: () => {
+ if (this.app && this.app.isApplication) {
+ this.actor.set_opacity(255);
+ }
+ },
+ acceptDrop: (source) => {
+ if (source.isDraggableApp) {
+ if (this.app && this.app.isApplication && source.id !== this.app.id) {
+ this.actor.set_opacity(255);
+ this.appThis.addFavoriteAppToPos(source.id, this.app.id);
+ return true;
+ } else if (!(this.app && this.app.isApplication)) {
+ this.appThis.appFavorites.addFavorite(source.id);
+ return true;
+ }
+ return DragMotionResult.NO_DROP
+ } else if (source.isDraggableFile){
+ this.appThis.xappAddFavoriteFile(source.uri);
+ return true;
+ }
+ },
+ };
+
+ if (this.app && this.app.isApplication) { //make sidebar apps draggable
+ Object.assign(this.actor._delegate, {
+ getDragActorSource: () => this.actor,
+ _getDragActor: () => new Clutter.Clone({source: this.actor}),
+ getDragActor: () => new Clutter.Clone({source: this.icon}),
+ id: this.app.id,
+ isDraggableApp: true
+ });
+
+ this.draggable = makeDraggable(this.actor);
+ this.signals.connect(this.draggable, 'drag-begin', () => hideTooltipIfVisible());
+ }
+
+ this.signals.connect(this.actor, 'enter-event', this.handleEnter.bind(this));
+ this.signals.connect(this.actor, 'leave-event', this.handleLeave.bind(this));
+ this.signals.connect(this.actor, 'button-release-event', this._handleButtonRelease.bind(this));
+ }
+
+ _handleButtonRelease(actor, e) {
+ if (this.appThis.display.contextMenu.isOpen) {
+ this.appThis.display.contextMenu.close();
+ this.appThis.display.clearFocusedActors();
+ this.handleEnter();
+ return Clutter.EVENT_STOP;
+ }
+
+ const button = e.get_button();
+ if (button === Clutter.BUTTON_PRIMARY) {
+ this.activate();
+ return Clutter.EVENT_STOP;
+ } else if (button === Clutter.BUTTON_SECONDARY) {
+ if (this.app != null) {
+ this.openContextMenu(e);
+ }
+ return Clutter.EVENT_STOP;
+ }
+
+ return Clutter.EVENT_PROPAGATE;
+ }
+
+ activate() {
+ if (this.callback) {
+ this.callback();
+ } else if (this.app.isApplication) {
+ this.appThis.recentApps.add(this.app.id);
+ this.app.open_new_window(-1);
+ this.appThis.menu.close();
+ } else if (this.app.isFavoriteFile) {
+ try {
+ Gio.app_info_launch_default_for_uri(this.app.uri, global.create_app_launch_context());
+ this.appThis.menu.close();
+ } catch (e) {
+ Main.notify(_('Error while opening file:'), e.message);
+ }
+ }
+ }
+
+ openContextMenu(e) {
+ hideTooltipIfVisible();
+ this.appThis.display.contextMenu.openAppContextMenu(this.app, e, this.actor);
+ }
+
+ handleEnter(actor, event) {
+ if (this.appThis.display.contextMenu.isOpen) {
+ return true;
+ }
+
+ if (event) {//mouse event
+ this.appThis.display.clearFocusedActors();
+ } else {//key nav
+ scrollToButton(this, this.appThis.settings.enableAnimation);
+ }
+
+ this.has_focus = true;
+ this.actor.add_style_pseudo_class('hover');
+
+ //show tooltip
+ if (this.appThis.settings.descriptionPlacement === DescriptionPlacement.NONE) {
+ return Clutter.EVENT_STOP;
+ }
+ let [x, y] = this.actor.get_transformed_position();
+ x += this.actor.width + 2 * global.ui_scale;
+ y += this.actor.height + 6 * global.ui_scale;
+ let text = `${this.name}`;
+ if (this.description) {
+ text += '\n' + wordWrap(this.description) + '';
+ }
+ text = text.replace(/&/g, '&');
+ showTooltip(this.actor, x, y, false /*don't center tooltip on x*/, text);
+ return Clutter.EVENT_STOP;
+ }
+
+ handleLeave() {
+ if (this.appThis.display.contextMenu.isOpen) {
+ return true;
+ }
+ this.has_focus = false;
+ this.actor.remove_style_pseudo_class('hover');
+ hideTooltipIfVisible();
+ return true;
+ }
+
+ destroy() {
+ this.signals.disconnectAllSignals();
+
+ if (this.icon) {
+ this.icon.destroy();
+ }
+ this.actor.destroy();
+ }
+}
+
+class Separator { //creates a faint line (St.BoxLayout) used to separate items on the sidebar
+ constructor (appThis) {
+ this.appThis = appThis;
+ this.separator = new St.BoxLayout({x_expand: false, y_expand: false});
+
+ const getThemeForegroundColor = () => {
+ return this.appThis.menu.actor.get_theme_node().get_foreground_color().to_string().substring(0, 7);
+ }
+
+ let width = this.appThis.settings.sidebarIconSize + 8;
+ let height = 2;
+ if (this.appThis.settings.sidebarPlacement === SidebarPlacement.TOP ||
+ this.appThis.settings.sidebarPlacement === SidebarPlacement.BOTTOM) {
+ [width, height] = [height, width];
+ }
+ this.separator.style = `width: ${width}px; height: ${height}px; background-color: ${
+ getThemeForegroundColor()}; margin: 1px; border: 0px; border-radius: 10px; `;
+ this.separator.set_opacity(35);
+ }
+
+ destroy() {
+ this.separator.destroy();
+ }
+}
+
+//Creates the sidebar. Creates SidebarButtons and populates the sidebar.
+class Sidebar {
+ constructor (appThis) {
+ this.appThis = appThis;
+ this.items = [];
+ this.innerBox = new St.BoxLayout({
+ vertical: (this.appThis.settings.sidebarPlacement === SidebarPlacement.LEFT
+ || this.appThis.settings.sidebarPlacement === SidebarPlacement.RIGHT)
+ });
+
+ this.sidebarScrollBox = new St.ScrollView({ y_align: St.Align.MIDDLE, style_class: 'vfade gridmenu-sidebar-scrollbox' });
+ this.sidebarScrollBox.add_actor(this.innerBox);
+ this.sidebarScrollBox.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER);
+ this.sidebarScrollBox.set_clip_to_allocation(true);
+ this.sidebarScrollBox.set_auto_scrolling(this.appThis.settings.enableAutoScroll);
+ this.sidebarScrollBox.set_mouse_scrolling(true);
+ this.sidebarOuterBox = new St.BoxLayout({style_class: 'gridmenu-sidebar-box'});
+ this.sidebarOuterBox.add(this.sidebarScrollBox, { });
+ if (!this.appThis.settings.showSidebar) {
+ this.sidebarScrollBox.width = 0;
+ this.sidebarScrollBox.height = 0;
+ }
+ }
+
+ populate () {
+ this.innerBox.remove_all_children();
+ this.items.forEach(item => item.destroy());
+ this.items = [];
+ this.separator1Position = null;
+ this.separator2Position = null;
+ if (this.separator1) {
+ this.separator1.destroy();
+ this.separator1 = null;
+ }
+ if (this.separator2) {
+ this.separator2.destroy();
+ this.separator2 = null;
+ }
+ //----add session buttons to this.items[]
+ const newSidebarIcon = (iconName) => {
+ return new St.Icon({
+ icon_name: iconName,
+ icon_size: this.appThis.settings.sidebarIconSize,
+ icon_type: this.appThis.settings.sidebarIconSize <= 24 ?
+ St.IconType.SYMBOLIC : St.IconType.FULLCOLOR
+ });
+ };
+ this.items.push(new SidebarButton(
+ this.appThis,
+ newSidebarIcon('system-shutdown'),
+ null,
+ _('Quit'),
+ _('Shutdown the computer'),
+ () => {
+ this.appThis.menu.close();
+ this.appThis.sessionManager.ShutdownRemote();
+ }
+ ));
+ this.items.push(new SidebarButton(
+ this.appThis, newSidebarIcon('system-log-out'),
+ null,
+ _('Logout'),
+ _('Leave the session'),
+ () => {
+ this.appThis.menu.close();
+ this.appThis.sessionManager.LogoutRemote(0);
+ }
+ ));
+ this.items.push(new SidebarButton(
+ this.appThis,
+ newSidebarIcon('system-lock-screen'),
+ null, _('Lock screen'),
+ _('Lock the screen'),
+ () => {
+ const screensaver_settings = new Gio.Settings({
+ schema_id: 'org.cinnamon.desktop.screensaver' });
+ const screensaver_dialog = Gio.file_new_for_path('/usr/bin/cinnamon-screensaver-command');
+ if (screensaver_dialog.query_exists(null)) {
+ if (screensaver_settings.get_boolean('ask-for-away-message')) {
+ Util.spawnCommandLine('cinnamon-screensaver-lock-dialog');
+ } else {
+ Util.spawnCommandLine('cinnamon-screensaver-command --lock');//
+ }
+ } else {
+ this.screenSaverProxy.LockRemote('');
+ }
+ this.appThis.menu.close();
+ }
+ ));
+ //----add favorite apps to this.items[]
+ if (this.appThis.settings.sidebarFavorites === 1 //Apps only
+ || this.appThis.settings.sidebarFavorites === 3) { // Apps and files
+ this.appThis.listFavoriteApps().forEach(fav => {
+ if (!this.separator1Position) {
+ this.separator1Position = this.items.length;
+ }
+ this.items.push(new SidebarButton(
+ this.appThis,
+ fav.create_icon_texture(this.appThis.settings.sidebarIconSize),
+ fav,
+ fav.name,
+ fav.description,
+ null
+ ));
+ });
+ }
+ //----add favorite files to this.items[]
+ if (this.appThis.settings.sidebarFavorites === 2 //Files only
+ || this.appThis.settings.sidebarFavorites === 3) { // Apps and files
+ this.appThis.listFavoriteFiles().forEach(fav => {
+ if (!this.separator2Position) {
+ this.separator2Position = this.items.length;
+ }
+ let gicon = getThumbnail_gicon(fav.uri, fav.mimeType) || fav.gicon;
+ this.items.push(new SidebarButton(
+ this.appThis,
+ new St.Icon({ gicon: gicon, icon_size: this.appThis.settings.sidebarIconSize}),
+ fav,
+ fav.name,
+ fav.description,
+ null
+ ));
+ });
+ }
+ //----change order of all items depending on buttons placement
+ const reverseOrder = this.appThis.settings.sidebarPlacement === SidebarPlacement.LEFT ||
+ this.appThis.settings.sidebarPlacement === SidebarPlacement.RIGHT;
+ if (reverseOrder) {
+ this.items.reverse();
+ }
+
+ if (this.separator1Position) {
+ this.separator1 = new Separator(this.appThis);
+ }
+ if (this.separator2Position) {
+ this.separator2 = new Separator(this.appThis);
+ }
+
+ //----populate box with items[]
+ for (let i = 0; i < this.items.length; i++) {
+ if (this.separator1Position &&
+ ((reverseOrder && i == this.items.length - this.separator1Position) ||
+ (!reverseOrder && i === this.separator1Position))){
+ this.innerBox.add(this.separator1.separator, {
+ x_fill: false,
+ y_fill: false,
+ x_align: St.Align.MIDDLE,
+ y_align: St.Align.MIDDLE
+ });
+ }
+ if (this.separator2Position &&
+ ((reverseOrder && i == this.items.length - this.separator2Position) ||
+ (!reverseOrder && i === this.separator2Position))){
+ this.innerBox.add(this.separator2.separator, {
+ x_fill: false,
+ y_fill: false,
+ x_align: St.Align.MIDDLE,
+ y_align: St.Align.MIDDLE
+ });
+ }
+ this.innerBox.add(this.items[i].actor, {
+ x_fill: false,
+ y_fill: false,
+ x_align: St.Align.MIDDLE,
+ y_align: St.Align.MIDDLE
+ });
+ }
+
+ return;
+ }
+
+ scrollToQuitButton() {
+ scrollToButton(this.items[this.items.length - 1], false);
+ }
+
+ getButtons() {
+ return this.items;
+ }
+
+ clearSidebarFocusedActors() {
+ const foundItem = this.items.findIndex(button => button.has_focus);
+ if (foundItem > -1) {
+ this.items[foundItem].handleLeave();
+ }
+ }
+
+ destroy() {
+ this.items.forEach(item => item.destroy());
+ this.items = null;
+ if (this.separator1) {
+ this.separator1.destroy();
+ }
+ if (this.separator2) {
+ this.separator2.destroy();
+ }
+ this.innerBox.destroy();
+ this.sidebarScrollBox.destroy();
+ this.sidebarOuterBox.destroy();
+ }
+}
+
+module.exports = {Sidebar};
diff --git a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/stylesheet.css b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/stylesheet.css
new file mode 100755
index 0000000000..85639504e4
--- /dev/null
+++ b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/stylesheet.css
@@ -0,0 +1,54 @@
+/*css selectors used by gridmenu (icon-menu)
+┌──.menu-background .gridmenu .sidebar-top .sidebar-bottom .sidebar-left .sidebar-right──────
+│┌──.menu-applications-outer-box .menu-applications-box──────────────────────────────────────────────────────────────────────────
+││┌──.gridmenu-middle-pane───────────────────────────────────────────────────────────────────────────────────────────
+│││┌──.gridmenu-sidebar-box─────────────────┐┌──.menu-categories-scrollbox .vfade──┐┌──.menu-applications-scrollbox .vfade───────
+││││┌──.gridmenu-sidebar-scrollbox .vfade──┐││┌──.menu-categories-box─────────────┐││┌──.menu-applications-inner-box──────────
+│││││ ││││ ││││┌──.menu-applications-header-text───┐
+│││││ ││││ ││││└───────────────────────────────────┘
+││││└─────── ││││ ││││┌──.menu-applications-grid-box────────┐
+│││└─────── ││││ ││││└─────────────────────────────────────┘
+││└──────────────────
+││┌──.menu-search-box─────────
+│││┌──#menu-search-entry──────────
+*/
+
+.menu-category-button:hover { /*Only used when "activate categories on click" option is set.*/
+ background-color: rgba(128,128,128,0.2);
+ border-radius: 4px;
+ border-image: none;
+}
+
+.menu-category-button:highlighted, /*Used to highlight category containing newly installed apps.*/
+.menu-category-button-selected:highlighted {
+ font-weight: bold;
+}
+
+.gridmenu .menu-applications-inner-box {
+ padding-left: 0px;
+ padding-right: 0px;
+}
+
+.menu-applications-grid-box .menu-application-button,
+.menu-applications-grid-box .menu-application-button-selected {
+ padding: 10px 2px;
+}
+
+.menu-applications-grid-box .menu-application-button-label,
+.menu-applications-grid-box .menu-application-button-label:ltr,
+.menu-applications-grid-box .menu-application-button-label:rtl {
+ text-align: center;
+ padding-left: 0px;
+ padding-right: 0px;
+}
+
+.gridmenu .menu-categories-box {
+ padding-left: 6px;
+ padding-right: 6px;
+}
+
+.gridmenu .menu-search-box, .gridmenu .menu-search-box:ltr, .gridmenu .menu-search-box:rtl {
+ min-width: 160px;
+ padding: 0px 8px;
+}
+
diff --git a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/utils.js b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/utils.js
new file mode 100755
index 0000000000..07e6117238
--- /dev/null
+++ b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/utils.js
@@ -0,0 +1,254 @@
+const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
+const Mainloop = imports.mainloop;
+const St = imports.gi.St;
+const Main = imports.ui.main;
+const ByteArray = imports.byteArray;
+const {addTween} = imports.ui.tweener;
+
+const wordWrap = text => text.match( /.{1,80}(\s|$|-|=|\+|_|&|\\)|\S+?(\s|$|-|=|\+|_|&|\\)/g ).join('\n');
+
+const graphemeBaseChars = s =>
+//decompose and remove discritics (blocks: Combining Diacritical Marks,
+//Combining Diacritical Marks Extended and Combining Diacritical Marks Supplement)
+ s.normalize('NFKD').replace(/[\u0300-\u036F\u1AB0-\u1AFF\u1DC0-\u1DFF]/g, "");
+
+function escapeRegExp(str) {
+ // from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
+ return str.replace(/[-\/.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
+}
+
+//===========================================================
+
+const getThumbnail_gicon = (uri, mimeType) => {
+ //Note: this function doesn't check if thumbnail is up to date.
+ const file = Gio.File.new_for_uri(uri);
+ if (!file.query_exists(null)) {//check because it's possible for isFavoriteFile's to not exist.
+ return null;
+ }
+ //
+ const isImage = ['image/jpeg', 'image/png', 'image/svg+xml', 'image/tiff', 'image/bmp',
+ 'image/gif'].includes(mimeType);
+ const fileSize = file.query_info('standard::size', Gio.FileQueryInfoFlags.NONE, null).get_size();
+
+ //----Get thumbnail from cache
+ if (!(isImage && fileSize < 50000)) {//Don't bother with thumbnail cache if file is a
+ //small image, quicker to just create icon from file itself and avoids
+ //possible out of date cached thumbnail.
+ const ba = ByteArray.fromString(uri, 'UTF-8');
+ const md5 = GLib.Checksum.new(GLib.ChecksumType.MD5);
+ md5.update(ba);
+ const thumbDir = GLib.get_user_cache_dir() + '/thumbnails/';
+ const thumbName = md5.get_string() + '.png';
+ const thumbPathNormal = thumbDir + 'normal/' + thumbName;
+ const thumbPathLarge = thumbDir + 'large/' + thumbName;
+ if (GLib.file_test(thumbPathNormal, GLib.FileTest.EXISTS)) {
+ return new Gio.FileIcon({ file: Gio.file_new_for_path(thumbPathNormal) });
+ }
+ if (GLib.file_test(thumbPathLarge, GLib.FileTest.EXISTS)) {
+ return new Gio.FileIcon({ file: Gio.file_new_for_path(thumbPathLarge) });
+ }
+ }
+
+ //----No cached thumbnail available so make icon from image.
+ if (isImage && fileSize < 30000000) {//don't read image files > 30MB
+ return new Gio.FileIcon({ file: file });
+ }
+
+ //----No thumbnail
+ return null;
+};
+
+//============================================================
+
+let onlyOneTooltip = null;
+const showTooltip = (actor, xpos, ypos, center_on_xpos, text) => {
+ if (onlyOneTooltip) {
+ global.logWarning("gridmenu: Previous tooltip still exists...removing...");
+ onlyOneTooltip.destroy();
+ onlyOneTooltip = null;
+ }
+ onlyOneTooltip = new NewTooltip (actor, xpos, ypos, center_on_xpos, text);
+};
+
+const hideTooltipIfVisible = () => {
+ if (onlyOneTooltip) {
+ onlyOneTooltip.destroy();
+ onlyOneTooltip = null;
+ }
+};
+
+class NewTooltip {
+ constructor(actor, xpos, ypos, center_on_xpos /*boolean*/, text) {
+ this.actor = actor;
+ this.xpos = xpos;
+ this.ypos = ypos;
+ this.center_on_xpos = center_on_xpos;
+ this.text = text;
+ if (this.text && this.text !== '') {
+ this.showTimer = Mainloop.timeout_add(250, () => this.show());
+ }
+ }
+
+ show() {
+ this.showTimer = null;
+
+ this.tooltip = new St.Label({
+ name: 'Tooltip'
+ });
+ this.tooltip.show_on_set_parent = true;
+ Main.uiGroup.add_actor(this.tooltip);
+ this.tooltip.get_clutter_text().set_markup(this.text);
+ this.tooltip.set_style('text-align: left;');
+
+ let tooltipWidth = this.tooltip.get_allocation_box().x2 - this.tooltip.get_allocation_box().x1;
+ let tooltipHeight = this.tooltip.get_allocation_box().y2 - this.tooltip.get_allocation_box().y1;
+ let monitor = Main.layoutManager.findMonitorForActor(this.actor);
+ let tooltipLeft = this.xpos;
+ let tooltipTop = this.ypos;
+ if (this.center_on_xpos) {
+ tooltipLeft -= Math.floor(tooltipWidth / 3);
+ }
+ tooltipLeft = Math.max(tooltipLeft, monitor.x);
+ tooltipLeft = Math.min(tooltipLeft, monitor.x + monitor.width - tooltipWidth);
+ tooltipTop = Math.max(tooltipTop, monitor.y);
+ tooltipTop = Math.min(tooltipTop, monitor.y + monitor.height - tooltipHeight);
+
+ this.tooltip.set_position(tooltipLeft, tooltipTop);
+ this.tooltip.raise_top();
+ this.tooltip.show();
+ }
+
+ destroy() {
+ if (this.showTimer) {
+ Mainloop.source_remove(this.showTimer);
+ this.showTimer = null;
+ }
+ if (this.tooltip) {
+ this.tooltip.destroy();
+ this.tooltip = null;
+ }
+ }
+}
+
+//===================================================
+const searchStrPart = (q, str, noFuzzySearch, noSubStringSearch) => {
+ if (!str || !q) {
+ return 0; //match score = 0
+ }
+
+ const str2 = graphemeBaseChars(str).toLocaleUpperCase();
+ //q is already graphemeBaseChars().toLocaleUpperCase() in _doSearch()
+ let score = 0, bigrams_score = 0;
+
+ if (new RegExp('\\b'+escapeRegExp(q)).test(str2)) { //match substring from beginning of words
+ const foundPosition = str2.indexOf(q);
+ score = (foundPosition === 0) ? 1.21 : 1.2;//slightly higher score if from beginning
+ } else if (!noSubStringSearch && str2.indexOf(q) !== -1) { //else match substring
+ score = 1.1;
+ } else if (!noFuzzySearch){ //else fuzzy match
+ //find longest substring of str2 made up of letters from q
+ const found = str2.match(new RegExp('[' + escapeRegExp(q) + ']+','g'));
+ let length = 0;
+ let longest;
+ if (found) {
+ for(let i=0; i < found.length; i++){
+ if(found[i].length > length){
+ length = found[i].length;
+ longest = found[i];
+ }
+ }
+ }
+ if (longest) {
+ //get a score for similarity by counting 2 letter pairs (bigrams) that match
+ if (q.length >= 2) {
+ const max_bigrams = q.length -1;
+ let found_bigrams = 0;
+ for (let qi = 0; qi < max_bigrams; qi++ ) {
+ if (longest.indexOf(q[qi] + q[qi + 1]) >= 0) {
+ found_bigrams++;
+ }
+ }
+ bigrams_score = Math.min(found_bigrams / max_bigrams, 1);
+ } else {
+ bigrams_score = 1;
+ }
+
+ //return a fuzzy match score of between 0 and 1.
+ score = Math.min(longest.length / q.length, 1.0) * bigrams_score;
+ }
+ }
+
+ return score;
+};
+
+const searchStr = (q, str, noFuzzySearch = false, noSubStringSearch = false) => {
+ const separatorIndex = q.indexOf(" ");
+ if (separatorIndex < 1) {
+ return searchStrPart(q, str, noFuzzySearch, noSubStringSearch);
+ }
+
+ //There are two search terms separated by a space.
+ const part1Score = searchStrPart(q.slice(0, separatorIndex), str, noFuzzySearch, noSubStringSearch);
+ const part2Score = searchStrPart(q.slice(separatorIndex + 1), str, noFuzzySearch, noSubStringSearch);
+ const avgScore = (part1Score + part2Score) / 2.0;
+
+ return avgScore;
+};
+
+var scrollToButton = (button, enableAnimation) => {
+ let scrollBox = button.actor.get_parent();
+ let i = 0;
+ while (!(scrollBox instanceof St.ScrollView)) {
+ i++;
+ if (i > 10 || !scrollBox) {
+ global.logWarning('gridmenu: Unable to find scrollbox for' + button.actor.toString());
+ return false;
+ }
+ scrollBox = scrollBox.get_parent();
+ }
+
+ const adjustment = scrollBox.vscroll.adjustment;
+ let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();
+
+ let offset = 0;
+ const vfade = scrollBox.get_effect('fade');//this always seems to return null?
+ if (vfade) {
+ offset = vfade.vfade_offset;
+ }
+
+ const box = button.actor.get_allocation_box();
+ const y1 = box.y1, y2 = box.y2;
+ const PADDING_ALLOWANCE = 20; //In case button parent(s) have padding meaning y1 won't go to 0
+ if (y1 < value + offset) {
+ if (y1 < PADDING_ALLOWANCE) {
+ value = 0;
+ } else {
+ value = Math.max(0, y1 - offset);
+ }
+ } else if (y2 > value + pageSize - offset) {
+ if (y2 > upper - offset - PADDING_ALLOWANCE) {
+ value = upper - pageSize;
+ } else {
+ value = Math.min(upper, y2 + offset - pageSize);
+ }
+ } else {
+ return false;
+ }
+
+ if (enableAnimation) {
+ addTween(adjustment, {value: value, time: 0.1, transition: 'easeOutQuad'});
+ } else {
+ adjustment.set_value(value);
+ }
+}
+
+module.exports = {
+ wordWrap,
+ graphemeBaseChars,
+ getThumbnail_gicon,
+ showTooltip,
+ hideTooltipIfVisible,
+ searchStr,
+ scrollToButton
+};
From c7343d12452811f3d6ac1298bd0b14d0f612263d Mon Sep 17 00:00:00 2001
From: fredcw <58893963+fredcw@users.noreply.github.com>
Date: Wed, 4 Jun 2025 18:26:07 +0100
Subject: [PATCH 2/3] fix minor UI bug
---
.../applets/icon-menu@cinnamon.org/applet.js | 12 +++++++-----
.../applets/icon-menu@cinnamon.org/display.js | 11 ++++++-----
.../applets/icon-menu@cinnamon.org/sidebar.js | 4 ----
3 files changed, 13 insertions(+), 14 deletions(-)
diff --git a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/applet.js b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/applet.js
index 60f3b7467c..b1c3302b92 100644
--- a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/applet.js
+++ b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/applet.js
@@ -409,10 +409,10 @@ class CinnamenuApplet extends TextIconApplet {
_onOpenStateToggled(menu, open) {
if (global.settings.get_boolean('panel-edit-mode')) {
- return false;
+ return;
}
if (!open) {
- return true; // this._onMenuClosed() is called on 'menu-animated-closed' signal to handle closing.
+ return; // this._onMenuClosed() is called on 'menu-animated-closed' signal to handle closing.
}
if (this.openMenuTimeoutId) {
@@ -421,8 +421,10 @@ class CinnamenuApplet extends TextIconApplet {
}
this.display.categoriesView.update();//in case menu editor or enabled category changes.
- this.display.sidebar.populate();//in case fav files changed
- this.display.sidebar.scrollToQuitButton();//ensure quit button is visible
+ if (this.settings.showSidebar) {
+ this.display.sidebar.populate();//in case fav files changed
+ this.display.sidebar.scrollToQuitButton();//ensure quit button is visible
+ }
global.stage.set_key_focus(this.display.searchView.searchEntry);
if (this.currentCategory === 'places' && !this.settings.showPlaces ||
@@ -463,7 +465,7 @@ class CinnamenuApplet extends TextIconApplet {
this.display.appsView.focusFirstItem();
}
- return true;
+ return;
}
_onMenuClosed() {
diff --git a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/display.js b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/display.js
index baef65768e..ff27b0eb63 100644
--- a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/display.js
+++ b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/display.js
@@ -42,7 +42,8 @@ class Display {
(...args) => this.appThis._onMenuKeyPress(...args)
);
this.bottomPane = new St.BoxLayout({});
- if (sidebarPlacement === SidebarPlacement.TOP || sidebarPlacement === SidebarPlacement.BOTTOM) {
+ if (this.appThis.settings.showSidebar && (sidebarPlacement === SidebarPlacement.TOP ||
+ sidebarPlacement === SidebarPlacement.BOTTOM)) {
this.bottomPane.add(this.sidebar.sidebarOuterBox, {
expand: false,
x_fill: false,
@@ -63,7 +64,7 @@ class Display {
this.appsView = new AppsView(this.appThis);
this.categoriesView = new CategoriesView(this.appThis);
this.middlePane = new St.BoxLayout({style_class: 'gridmenu-middle-pane'});
- if (sidebarPlacement === SidebarPlacement.LEFT) {
+ if (this.appThis.settings.showSidebar && sidebarPlacement === SidebarPlacement.LEFT) {
this.middlePane.add(this.sidebar.sidebarOuterBox, {
expand: false,
x_fill: false,
@@ -85,7 +86,7 @@ class Display {
y_align: St.Align.START,
expand: false
});
- if (sidebarPlacement === SidebarPlacement.RIGHT) {
+ if (this.appThis.settings.showSidebar && sidebarPlacement === SidebarPlacement.RIGHT) {
this.middlePane.add(this.sidebar.sidebarOuterBox, {
expand: false,
x_fill: false,
@@ -225,8 +226,8 @@ class Display {
//find minimum width for categoriesView + sidebar (if present)
let leftSideWidth = this.categoriesView.groupCategoriesWorkspacesScrollBox.width;
- if (this.appThis.settings.sidebarPlacement === SidebarPlacement.LEFT ||
- this.appThis.settings.sidebarPlacement === SidebarPlacement.RIGHT) {
+ if (this.appThis.settings.showSidebar && (this.appThis.settings.sidebarPlacement === SidebarPlacement.LEFT ||
+ this.appThis.settings.sidebarPlacement === SidebarPlacement.RIGHT)) {
leftSideWidth += this.sidebar.sidebarOuterBox.width;
}
diff --git a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/sidebar.js b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/sidebar.js
index 1d0ac61475..465055526d 100644
--- a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/sidebar.js
+++ b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/sidebar.js
@@ -230,10 +230,6 @@ class Sidebar {
this.sidebarScrollBox.set_mouse_scrolling(true);
this.sidebarOuterBox = new St.BoxLayout({style_class: 'gridmenu-sidebar-box'});
this.sidebarOuterBox.add(this.sidebarScrollBox, { });
- if (!this.appThis.settings.showSidebar) {
- this.sidebarScrollBox.width = 0;
- this.sidebarScrollBox.height = 0;
- }
}
populate () {
From 7e66e056470917cd6e58cfa9248f501f782a41c1 Mon Sep 17 00:00:00 2001
From: fredcw <58893963+fredcw@users.noreply.github.com>
Date: Tue, 24 Jun 2025 12:17:52 +0100
Subject: [PATCH 3/3] Minor fixes
---
.../applets/icon-menu@cinnamon.org/applet.js | 36 ++++---------------
.../icon-menu@cinnamon.org/categoriesview.js | 2 +-
.../icon-menu@cinnamon.org/contextmenu.js | 24 +++++++++----
.../applets/icon-menu@cinnamon.org/display.js | 1 -
.../applets/icon-menu@cinnamon.org/sidebar.js | 3 +-
.../icon-menu@cinnamon.org/stylesheet.css | 8 +++++
6 files changed, 35 insertions(+), 39 deletions(-)
diff --git a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/applet.js b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/applet.js
index b1c3302b92..0f4a12bb82 100644
--- a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/applet.js
+++ b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/applet.js
@@ -125,6 +125,7 @@ class CinnamenuApplet extends TextIconApplet {
'menu-animated-closed',
this._onMenuClosed.bind(this)
);
+ this.signals.connect(XApp.Favorites.get_default(), 'changed', () => this._onXappFavoritesChange());
this.apps = new Apps(this.appSystem);
this.screenSaverProxy = new ScreenSaverProxy();
this.sessionManager = new GnomeSession.SessionManager();
@@ -133,9 +134,9 @@ class CinnamenuApplet extends TextIconApplet {
Main.keybindingManager.addHotKey(
'overlay-key-' + this.instance_id,
this.settings.overlayKey,
- () => {
+ () => {
if (Main.overview.visible || Main.expo.visible) return;
- if (!this.isOpen) {
+ if (!this.menu.isOpen) {
this.panel.peekPanel();
}
this.menu.toggle_with_options(this.settings.enableAnimation);
@@ -359,7 +360,9 @@ class CinnamenuApplet extends TextIconApplet {
}
}
- updateAfterXappFavoriteFileChange() {
+ _onXappFavoritesChange() {
+ if (!this.menu.isOpen) return;
+
this.display.sidebar.populate();
this.display.categoriesView.update();//in case fav files category needs adding/removing
this.display.updateMenuSize();
@@ -368,25 +371,6 @@ class CinnamenuApplet extends TextIconApplet {
}
}
- xappGetIsFavoriteFile(uri) {
- const favs = XApp.Favorites.get_default();
- return favs.find_by_uri(uri) !== null;
- }
-
- xappAddFavoriteFile(uri) {
- const favs = XApp.Favorites.get_default();
- favs.add(uri);
- //xapp favs list doesn't update synchronously after adding fav so add small
- //delay before updating menu.
- Mainloop.timeout_add(100, this.updateAfterXappFavoriteFileChange.bind(this));
- }
-
- xappRemoveFavoriteFile(uri) {
- const favs = XApp.Favorites.get_default();
- favs.remove(uri);
- this.updateAfterXappFavoriteFileChange();
- }
-
getIsFolderCategory(path) {
const index = this.settings.folderCategories.indexOf(path);
return index > -1;
@@ -750,14 +734,6 @@ class CinnamenuApplet extends TextIconApplet {
categoryButtons[focusedCategoryIndex].selectCategory();
}
return Clutter.EVENT_STOP;
- case symbol === Clutter.unicode_to_keysym("p".charCodeAt(0)) && ctrlKey:
- if (focusedAppItemExists && appButtons[focusedAppItemIndex].app.isApplication) {
- const desktop_file_path = appButtons[focusedAppItemIndex].app.desktop_file_path;
- Util.spawn(['cinnamon-desktop-editor', '-mlauncher', '-o' + desktop_file_path]);
- this.menu.close();
- return Clutter.EVENT_STOP;
- }
- return Clutter.EVENT_PROPAGATE
case (symbol === Clutter.KEY_Up || symbol === Clutter.KEY_KP_Up) && noModifiers:
leaveCurrentlyFocusedItem();
upNavigation();
diff --git a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/categoriesview.js b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/categoriesview.js
index 10da378776..67986ba5b6 100644
--- a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/categoriesview.js
+++ b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/categoriesview.js
@@ -338,7 +338,7 @@ class CategoriesView {
button = new CategoryButton(this.appThis, folder, displayName, null, gicon);
newButtons.push(button);
} catch(e) {
- log("gridmenu:Error creating folder category: " + folder + " ...skipping.");
+ global.log("gridmenu: Error creating folder category: " + folder + " ...skipping.");
//remove this error causing element from the array.
folderCategories.splice(index, 1);
folderCategoriesChanged = true;
diff --git a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/contextmenu.js b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/contextmenu.js
index 3e5106f1d5..170756c688 100644
--- a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/contextmenu.js
+++ b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/contextmenu.js
@@ -2,6 +2,7 @@ const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const St = imports.gi.St;
const Clutter = imports.gi.Clutter;
+const XApp = imports.gi.XApp;
const Main = imports.ui.main;
const {PopupBaseMenuItem, PopupMenu, PopupSeparatorMenuItem} = imports.ui.popupMenu;
const {getUserDesktopDir, changeModeGFile} = imports.misc.fileUtils;
@@ -239,7 +240,7 @@ class ContextMenu {
file.copy( destFile, 0, null, null);
changeModeGFile(destFile, 755);
} catch(e) {
- global.logError('gridmenu: Error creating desktop file', e);
+ global.logError('gridmenu: Error creating desktop file', e.message);
}
this.close();
}
@@ -277,9 +278,20 @@ class ContextMenu {
//show app info
if (this.appThis._pamacManagerAvailable) {
addMenuItem( new ContextMenuItem(this.appThis, _('App Info'), 'dialog-information',
- () => { spawnCommandLine("/usr/bin/pamac-manager --details-id=" + app.id);
- this.appThis.menu.close(); } ));
+ () => {
+ spawnCommandLine("/usr/bin/pamac-manager --details-id=" + app.id);
+ this.appThis.menu.close();
+ }
+ ));
}
+
+ //Properties
+ addMenuItem( new ContextMenuItem(this.appThis, _('Properties'), 'document-properties-symbolic',
+ () => {
+ spawnCommandLine('cinnamon-desktop-editor -mlauncher -o ' + app.desktop_file_path);
+ this.appThis.menu.close();
+ }
+ ));
}
_populateContextMenu_files(app) {
@@ -331,17 +343,17 @@ class ContextMenu {
//add/remove favorite
this.menu.addMenuItem(new PopupSeparatorMenuItem(this.appThis));
- if (this.appThis.xappGetIsFavoriteFile(app.uri)) { //favorite
+ if (XApp.Favorites.get_default().find_by_uri(app.uri) !== null) { //favorite
addMenuItem( new ContextMenuItem(this.appThis, _('Remove from favorites'), 'starred',
() => {
- this.appThis.xappRemoveFavoriteFile(app.uri);
+ XApp.Favorites.get_default().remove(app.uri);
this.close();
}
));
} else {
addMenuItem( new ContextMenuItem(this.appThis, _('Add to favorites'), 'non-starred',
() => {
- this.appThis.xappAddFavoriteFile(app.uri);
+ XApp.Favorites.get_default().add(app.uri);
this.close();
}
));
diff --git a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/display.js b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/display.js
index ff27b0eb63..eab370bde7 100644
--- a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/display.js
+++ b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/display.js
@@ -106,7 +106,6 @@ class Display {
reactive: true,
show_on_set_parent: false
});
- this.mainBox.add_style_class_name('menu-applications-box'); //this is to support old themes
if (sidebarPlacement === SidebarPlacement.TOP && this.appThis.settings.showSidebar) {
this.mainBox.add(this.bottomPane);
}
diff --git a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/sidebar.js b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/sidebar.js
index 465055526d..a872ac7fe2 100644
--- a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/sidebar.js
+++ b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/sidebar.js
@@ -4,6 +4,7 @@ const Atk = imports.gi.Atk;
const Clutter = imports.gi.Clutter;
const Util = imports.misc.util;
const St = imports.gi.St;
+const XApp = imports.gi.XApp;
const Main = imports.ui.main;
const {SignalManager} = imports.misc.signalManager;
const {DragMotionResult, makeDraggable} = imports.ui.dnd;
@@ -69,7 +70,7 @@ class SidebarButton {
}
return DragMotionResult.NO_DROP
} else if (source.isDraggableFile){
- this.appThis.xappAddFavoriteFile(source.uri);
+ XApp.Favorites.get_default().add(source.uri);
return true;
}
},
diff --git a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/stylesheet.css b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/stylesheet.css
index 85639504e4..c7481b1fb8 100755
--- a/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/stylesheet.css
+++ b/files/usr/share/cinnamon/applets/icon-menu@cinnamon.org/stylesheet.css
@@ -13,6 +13,14 @@
│││┌──#menu-search-entry──────────
*/
+/* The reason that some style css is in this file rather than in cinnamon's default theme is
+ * so that the user theme does not override the more specific selectors here with less specific
+ * selectors. For instance, ".gridmenu .menu-categories-box" in the default theme would have no
+ * effect if ".menu-categories-box" is defined in the user theme. By putting it here, it still
+ * takes effect, unless ".gridmenu .menu-categories-box" specifically, is overridden in the
+ * user theme.
+ */
+
.menu-category-button:hover { /*Only used when "activate categories on click" option is set.*/
background-color: rgba(128,128,128,0.2);
border-radius: 4px;