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:
commit
a9a1574344
7 changed files with 496 additions and 0 deletions
33
LICENSE
Normal file
33
LICENSE
Normal 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
30
README.md
Normal 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
28
e-module.edc
Normal 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
57
meson.build
Normal 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
6
module.desktop
Normal 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
322
src/e_mod_main.c
Normal 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
20
src/e_mod_main.h
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue