Initial commit: Everything Emoji module

Enlightenment Everything plugin to search and copy emoji via the ':' trigger.
Parses Unicode emoji-test.txt at startup, with fuzzy matching and the built-in
clipboard action.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
nemunaire 2026-04-11 15:09:47 +07:00
commit a9a1574344
7 changed files with 496 additions and 0 deletions

33
LICENSE Normal file
View file

@ -0,0 +1,33 @@
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies of the Software and its Copyright notices. In addition publicly
documented acknowledgment must be given that this software has been used if no
source code of this software is made available publicly. Making the source
available publicly means including the source for this software with the
distribution, or a method to get this software via some reasonable mechanism
(electronic transfer via a network or media) as well as making an offer to
supply the source on request. This Copyright notice serves as an offer to
supply the source on on request as well. Instead of this, supplying
acknowledgments of use of this software in either Copyright notices, Manuals,
Publicity and Marketing documents or any documentation provided with any
product containing this software. This License does not apply to any software
that links to the libraries provided by this software (statically or
dynamically), but only to the software provided.
Please see the COPYING-PLAIN for a plain-english explanation of this notice
and its intent.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

30
README.md Normal file
View file

@ -0,0 +1,30 @@
# Everything Emoji
An [Enlightenment](https://www.enlightenment.org/) Everything module to search and copy emoji to the clipboard.
Type `:` followed by an emoji name (e.g. `:smile`, `:fire`, `:thumbs up`) to fuzzy-search through all Unicode emoji. Select one to copy it to your clipboard.
## Dependencies
- Enlightenment (>= 0.27)
- [Everything](https://github.com/OpenMandrivaAssociation/everything) module
- Unicode emoji data (`emoji-test.txt`), typically provided by a package such as `unicode-data` or `unicode-emoji`
## Build & Install
```sh
meson setup build
ninja -C build
sudo ninja -C build install
```
Then enable **Everything Emoji** in Enlightenment's module settings.
## Configuration
- **Trigger**: `:` (configurable in Everything settings)
- **Minimum query length**: 2 characters
## License
See the [COPYING](COPYING) file.

28
e-module.edc Normal file
View file

@ -0,0 +1,28 @@
collections
{
group
{
name: "icon";
max: 128 128;
parts
{
part
{
name: "image";
mouse_events: 0;
type: TEXT;
description
{
state: "default" 0.0;
color: 255 255 255 255;
text
{
text: "😀";
font: "Sans";
size: 64;
}
}
}
}
}
}

57
meson.build Normal file
View file

@ -0,0 +1,57 @@
project('evry-emoji', 'c',
version: '0.1.0',
default_options: ['c_std=gnu11', 'warning_level=2'],
)
cc = meson.get_compiler('c')
dep_e = dependency('enlightenment')
dep_evry = dependency('everything')
emoji_test_paths = [
'/usr/share/unicode/emoji/emoji-test.txt',
'/usr/share/unicode-data/emoji-test.txt',
]
emoji_test_file = ''
foreach p : emoji_test_paths
if import('fs').exists(p)
emoji_test_file = p
break
endif
endforeach
if emoji_test_file == ''
error('Could not find emoji-test.txt. Searched: ' + ', '.join(emoji_test_paths))
endif
message('Found emoji data: ' + emoji_test_file)
module_arch = dep_e.get_variable(pkgconfig: 'module_arch')
module_dir = dep_e.get_variable(pkgconfig: 'modules')
install_dir = join_paths(module_dir, 'evry-emoji', module_arch)
data_dir = join_paths(module_dir, 'evry-emoji')
edje_cc = find_program('edje_cc')
edje_file = custom_target('e-module.edj',
input: 'e-module.edc',
output: 'e-module.edj',
command: [edje_cc, '-v', '@INPUT@', '@OUTPUT@'],
install: true,
install_dir: data_dir,
)
shared_module('module',
sources: ['src/e_mod_main.c'],
dependencies: [dep_e, dep_evry],
c_args: ['-DEMOJI_TEST_FILE="' + emoji_test_file + '"'],
name_prefix: '',
install: true,
install_dir: install_dir,
)
install_data('module.desktop',
install_dir: data_dir,
)

6
module.desktop Normal file
View file

@ -0,0 +1,6 @@
[Desktop Entry]
Type=Link
Name=Everything Emoji
Icon=e-module
Comment=Search and copy emoji to clipboard. Trigger ':' followed by emoji name.
X-Enlightenment-ModuleType=launcher

322
src/e_mod_main.c Normal file
View file

@ -0,0 +1,322 @@
#include "e.h"
#include "e_mod_main.h"
#include "evry_api.h"
/* #undef DBG
* #define DBG(...) ERR(__VA_ARGS__) */
typedef struct _Plugin Plugin;
typedef struct _Emoji Emoji;
struct _Plugin
{
Evry_Plugin base;
};
struct _Emoji
{
char *emoji; /* the emoji character(s) (UTF-8) */
char *name; /* descriptive name */
};
static const Evry_API *evry = NULL;
static Evry_Module *evry_module = NULL;
static Evry_Plugin *_plug = NULL;
static Eina_List *emoji_list = NULL; /* list of Emoji* */
/***************************************************************************/
/* emoji-test.txt parser */
/***************************************************************************/
static void
_emoji_free(Emoji *em)
{
if (!em) return;
free(em->emoji);
free(em->name);
free(em);
}
/* Convert space-separated hex codepoints (e.g. "1F600") to a UTF-8 string */
static char *
_codepoints_to_utf8(const char *hex_str)
{
char buf[64];
int pos = 0;
const char *p = hex_str;
while (*p && pos < (int)sizeof(buf) - 5)
{
unsigned long cp;
char *end;
while (*p == ' ') p++;
if (!*p) break;
cp = strtoul(p, &end, 16);
if (end == p) break;
p = end;
/* encode codepoint as UTF-8 */
if (cp <= 0x7F)
{
buf[pos++] = (char)cp;
}
else if (cp <= 0x7FF)
{
buf[pos++] = (char)(0xC0 | (cp >> 6));
buf[pos++] = (char)(0x80 | (cp & 0x3F));
}
else if (cp <= 0xFFFF)
{
buf[pos++] = (char)(0xE0 | (cp >> 12));
buf[pos++] = (char)(0x80 | ((cp >> 6) & 0x3F));
buf[pos++] = (char)(0x80 | (cp & 0x3F));
}
else if (cp <= 0x10FFFF)
{
buf[pos++] = (char)(0xF0 | (cp >> 18));
buf[pos++] = (char)(0x80 | ((cp >> 12) & 0x3F));
buf[pos++] = (char)(0x80 | ((cp >> 6) & 0x3F));
buf[pos++] = (char)(0x80 | (cp & 0x3F));
}
}
buf[pos] = '\0';
return strdup(buf);
}
static void
_emoji_list_load(void)
{
FILE *f;
char line[1024];
const char *path = EMOJI_TEST_FILE;
if (emoji_list) return;
f = fopen(path, "r");
if (!f)
{
ERR("evry-emoji: cannot open %s", path);
return;
}
while (fgets(line, sizeof(line), f))
{
char *semi, *hash, *name_start, *emoji_str;
Emoji *em;
/* skip comments and blank lines */
if (line[0] == '#' || line[0] == '\n' || line[0] == '\r')
continue;
/* format: "1F600 ; fully-qualified # 😀 E1.0 grinning face" */
semi = strchr(line, ';');
if (!semi) continue;
/* only keep fully-qualified emoji */
if (!strstr(semi, "fully-qualified"))
continue;
hash = strchr(semi, '#');
if (!hash) continue;
/* skip "# <emoji> E<version> " to get the name */
name_start = hash + 2; /* skip "# " */
/* skip the emoji character(s) and space */
while (*name_start && *name_start != ' ') name_start++;
if (*name_start == ' ') name_start++;
/* skip E<version> and space */
if (*name_start == 'E')
{
while (*name_start && *name_start != ' ') name_start++;
if (*name_start == ' ') name_start++;
}
/* trim trailing newline */
{
char *nl = strchr(name_start, '\n');
if (nl) *nl = '\0';
nl = strchr(name_start, '\r');
if (nl) *nl = '\0';
}
if (!*name_start) continue;
/* extract codepoints (before the semicolon) */
*semi = '\0';
/* trim trailing spaces from codepoint string */
{
char *end = semi - 1;
while (end > line && *end == ' ') *end-- = '\0';
}
emoji_str = _codepoints_to_utf8(line);
if (!emoji_str || !*emoji_str)
{
free(emoji_str);
continue;
}
em = calloc(1, sizeof(Emoji));
em->emoji = emoji_str;
em->name = strdup(name_start);
emoji_list = eina_list_append(emoji_list, em);
}
fclose(f);
INF("evry-emoji: loaded %d emoji", eina_list_count(emoji_list));
}
static void
_emoji_list_free(void)
{
Emoji *em;
EINA_LIST_FREE(emoji_list, em)
_emoji_free(em);
}
/***************************************************************************/
/* plugin callbacks */
/***************************************************************************/
static Evry_Plugin *
_begin(Evry_Plugin *plugin, const Evry_Item *it EINA_UNUSED)
{
Plugin *p;
EVRY_PLUGIN_INSTANCE(p, plugin);
return EVRY_PLUGIN(p);
}
static void
_finish(Evry_Plugin *plugin)
{
GET_PLUGIN(p, plugin);
EVRY_PLUGIN_ITEMS_FREE(p);
E_FREE(p);
}
static Evas_Object *
_icon_get(Evry_Item *it, Evas *e)
{
Evas_Object *o;
if (!it || !it->label) return NULL;
o = evas_object_text_add(e);
evas_object_text_font_set(o, "Sans", 24);
evas_object_text_text_set(o, it->data ? (const char *)it->data : "");
evas_object_show(o);
return o;
}
static int
_fetch(Evry_Plugin *plugin, const char *input)
{
GET_PLUGIN(p, plugin);
Eina_List *l;
Emoji *em;
EVRY_PLUGIN_ITEMS_FREE(p);
if (!input || !*input) return 0;
EINA_LIST_FOREACH(emoji_list, l, em)
{
int score;
score = evry->fuzzy_match(em->name, input);
if (!score) continue;
Evry_Item *it = EVRY_ITEM_NEW(Evry_Item, p, em->emoji, _icon_get, NULL);
if (!it) continue;
it->fuzzy_match = score;
EVRY_ITEM_DETAIL_SET(it, em->name);
EVRY_PLUGIN_ITEM_APPEND(p, it);
if (eina_list_count(EVRY_PLUGIN(p)->items) >= 100) break;
}
return EVRY_PLUGIN_HAS_ITEMS(p);
}
/***************************************************************************/
/* plugin init/shutdown */
/***************************************************************************/
static int
_plugins_init(const Evry_API *_api)
{
evry = _api;
if (!evry->api_version_check(EVRY_API_VERSION))
return EINA_FALSE;
_emoji_list_load();
_plug = EVRY_PLUGIN_BASE(N_("Emoji"), "face-smile",
EVRY_TYPE_TEXT,
_begin, _finish, _fetch);
_plug->history = EINA_FALSE;
if (evry->plugin_register(_plug, EVRY_PLUGIN_SUBJECT, 5))
{
Plugin_Config *pc = _plug->config;
pc->view_mode = VIEW_MODE_LIST;
pc->trigger = eina_stringshare_add(":");
pc->trigger_only = EINA_TRUE;
pc->min_query = 2;
pc->aggregate = EINA_FALSE;
pc->top_level = EINA_TRUE;
}
return EINA_TRUE;
}
static void
_plugins_shutdown(void)
{
if (_plug)
EVRY_PLUGIN_FREE(_plug);
_plug = NULL;
}
/***************************************************************************/
/* module entry points */
/***************************************************************************/
EAPI E_Module_Api e_modapi =
{
E_MODULE_API_VERSION,
"evry-emoji"
};
EAPI void *
e_modapi_init(E_Module *m)
{
EVRY_MODULE_NEW(evry_module, evry, _plugins_init, _plugins_shutdown);
return m;
}
EAPI int
e_modapi_shutdown(E_Module *m EINA_UNUSED)
{
EVRY_MODULE_FREE(evry_module);
_emoji_list_free();
return 1;
}
EAPI int
e_modapi_save(E_Module *m EINA_UNUSED)
{
return 1;
}

20
src/e_mod_main.h Normal file
View file

@ -0,0 +1,20 @@
#ifndef E_MOD_MAIN_H
#define E_MOD_MAIN_H
#ifdef ENABLE_NLS
# include <libintl.h>
# define _(string) dgettext(PACKAGE, string)
#else
# define bindtextdomain(domain,dir)
# define bind_textdomain_codeset(domain,codeset)
# define _(string) (string)
#endif
#define N_(str) (str)
EAPI extern E_Module_Api e_modapi;
EAPI void *e_modapi_init (E_Module *m);
EAPI int e_modapi_shutdown (E_Module *m);
EAPI int e_modapi_save (E_Module *m);
#endif