Compare commits

..

No commits in common. "main" and "konater" have entirely different histories.

31 changed files with 138 additions and 464 deletions

View File

@ -3,16 +3,10 @@ Kaomoji picker. Browse a library of Japanese emoticons, click to copy, add your
![preview](preview.png)
## Install
1. Download .flatpak file from Releases: https://gitea.angeltech.jp/Angel-Technologies/MoeMoji/releases/
2. Install with `flatpak install ./moemoji.flatpak`
## Global shortcut
This app uses global shortcut (Meta+Shift+E) for easy access. Press once to show the window, press twice to hide the window.
App starts minimized by default and is hidden to the system tray; by design you are expected to press Meta+Shift+E to invoke the window, copy the emoticon and hide the window with the same shortcut, so it doesn't get in your way.
If you use KDE like me, you will be requested with global shortcut access on app launch. You can remap this shortcut to anything you want and later you can edit this shortcut from system settings.
If you use KDE like me, you will be requested with global shortcut access on app launch. You can remap this shortcut to anything you want and later access this shortcut from system settings.
![image](Screenshot_20260228_021628.png)
@ -31,15 +25,6 @@ All kaomojis are stored in a simple, accessible format:
To add your own category/emoticon, simply create a folder and a corresponding .txt file in either `$XDG_DATA_HOME/moemoji/kaomoji/` or `/usr/local/share/moemoji`.
If you use flatpak, the correct data path is `~/.var/app/jp.angeltech.MoeMoji/data/`.
So adding your own emoticons would look like this:
1. `cd ~/.var/app/jp.angeltech.MoeMoji/data/`
2. `mkdir -p moemoji/kaomoji`
3. `cd moemoji/kaomoji`
4. `mkdir your-category`
5. `echo emoticon > your-category/001.txt`
6. relaunch app
You can add large ASCII text files or any text you want; the app handles multiline texts and displays a neat little preview.
# Build
@ -51,16 +36,5 @@ Run dev build: `GSETTINGS_SCHEMA_DIR=./builddir/data ./builddir/src/moemoji`
# Flatpak packaging
`flatpak-builder --force-clean --user --install .flatpak-build jp.angeltech.MoeMoji.json`
`flatpak run jp.angeltech.MoeMoji`
# Creating a distributable package
`flatpak-builder build/ jp.angeltech.MoeMoji.json`
`flatpak build-export export build`
`flatpak build-bundle export moemoji.flatpak jp.angeltech.MoeMoji.json`
## Local manifest
If you want to test local changes to the source code, you should build with `dev.jp.angeltech.MoeMoji.json` manifest instead, as main manifest pulls a tarball from the remote source.
`flatpak-builder --force-clean --user --install .flatpak-build net.angeltech.MoeMoji.json`
`flatpak run net.angeltech.MoeMoji`

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="83px" height="83px" viewBox="0 0 83 83" version="1.1">
<g id="surface1" transform="translate(0,25.5)">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 60.59375 18.59375 C 57.492188 18.070312 54.054688 14.488281 52.515625 10.171875 C 51.640625 7.714844 51.308594 4.003906 51.828125 2.492188 C 52.292969 1.15625 53.476562 0.71875 53.992188 1.695312 C 54.074219 1.84375 54.1875 2.914062 54.246094 4.066406 C 54.445312 8 55.3125 10.605469 57.1875 12.925781 C 60.078125 16.496094 63.039062 16.558594 65.648438 13.105469 C 67.75 10.328125 68.503906 7.429688 68.171875 3.375 C 68.089844 2.332031 68.0625 1.3125 68.113281 1.105469 C 68.164062 0.898438 68.378906 0.554688 68.59375 0.339844 C 68.921875 0.0078125 69.050781 -0.0390625 69.441406 0.0234375 C 69.695312 0.0664062 70.023438 0.207031 70.164062 0.332031 C 71.117188 1.183594 71.554688 4.667969 71.097656 7.726562 C 70.832031 9.507812 70.578125 10.394531 69.933594 11.789062 C 68.1875 15.578125 65.566406 18.050781 62.773438 18.554688 C 61.675781 18.75 61.554688 18.753906 60.59375 18.59375 Z M 60.59375 18.59375 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 21.109375 19.128906 C 19.484375 18.882812 18.238281 18.300781 16.761719 17.101562 C 14.632812 15.375 12.847656 11.914062 12.398438 8.652344 C 12.117188 6.625 12.355469 3.445312 12.929688 1.511719 C 13.199219 0.605469 14.046875 0.222656 14.585938 0.761719 C 14.890625 1.070312 14.914062 1.65625 14.695312 3.128906 C 14.375 5.230469 14.984375 8.875 15.984375 10.878906 C 17.722656 14.367188 20.324219 16.226562 22.78125 15.742188 C 24.078125 15.484375 24.875 15.015625 26.082031 13.792969 C 26.957031 12.90625 27.285156 12.457031 27.710938 11.574219 C 28.722656 9.476562 29.195312 7.75 29.558594 4.820312 C 29.761719 3.183594 30.425781 2.359375 31.398438 2.546875 C 31.828125 2.628906 32.460938 3.296875 32.652344 3.875 C 33.320312 5.902344 31.867188 11.515625 29.902344 14.496094 C 28.652344 16.386719 26.777344 17.949219 24.894531 18.664062 C 24.171875 18.9375 22.351562 19.289062 21.90625 19.242188 C 21.820312 19.234375 21.464844 19.183594 21.109375 19.128906 Z M 21.109375 19.128906 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 37.5 25.976562 C 35.785156 25.4375 34.304688 23.425781 33.828125 20.988281 C 33.742188 20.539062 33.695312 19.609375 33.722656 18.882812 C 33.769531 17.726562 33.8125 17.539062 34.085938 17.246094 C 34.589844 16.703125 35.246094 16.859375 35.496094 17.582031 C 35.574219 17.808594 35.640625 18.328125 35.640625 18.730469 C 35.640625 20.203125 36.375 22.082031 37.320312 23.039062 C 38.269531 23.992188 38.84375 23.84375 39.617188 22.4375 C 39.847656 22.015625 40.242188 21.480469 40.484375 21.246094 C 40.871094 20.878906 41.023438 20.824219 41.609375 20.824219 L 42.285156 20.824219 L 43.277344 21.863281 C 43.890625 22.507812 44.371094 22.90625 44.535156 22.90625 C 44.929688 22.90625 45.695312 22.066406 46.113281 21.167969 C 46.488281 20.371094 46.632812 19.789062 46.867188 18.171875 C 47.019531 17.097656 47.242188 16.726562 47.839844 16.527344 C 48.414062 16.335938 48.878906 16.539062 49.296875 17.160156 C 49.675781 17.726562 49.6875 18.777344 49.339844 20.25 C 48.90625 22.070312 48.246094 23.320312 47.121094 24.449219 C 46.257812 25.3125 45.683594 25.570312 44.617188 25.570312 C 43.578125 25.570312 42.957031 25.300781 42.234375 24.546875 C 41.992188 24.289062 41.730469 24.082031 41.652344 24.082031 C 41.578125 24.085938 41.363281 24.3125 41.175781 24.589844 C 40.289062 25.882812 38.882812 26.414062 37.5 25.976562 Z M 37.5 25.976562 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,38.039216%,52.941179%);fill-opacity:1;" d="M 71.503906 31.695312 C 69.191406 31.105469 67.3125 29.238281 66.457031 26.679688 C 66.144531 25.746094 66.085938 25.363281 66.082031 24.210938 C 66.074219 21.390625 67.128906 19.191406 69.300781 17.488281 C 70.269531 16.726562 71.15625 16.277344 72.851562 15.679688 C 75.933594 14.59375 78.132812 15.03125 80.371094 17.171875 C 81.609375 18.351562 82.273438 19.402344 82.746094 20.921875 C 83.050781 21.902344 83.074219 22.136719 83.015625 23.589844 C 82.945312 25.414062 82.722656 26.1875 81.859375 27.660156 C 80.738281 29.574219 78.976562 30.90625 76.785156 31.503906 C 75.417969 31.875 72.609375 31.980469 71.503906 31.695312 Z M 71.503906 31.695312 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,38.039216%,52.941179%);fill-opacity:1;" d="M 5.992188 31.839844 C 2.863281 31.117188 0.972656 29.347656 0.242188 26.476562 C -0.0585938 25.292969 -0.078125 24.339844 0.175781 23.144531 C 0.859375 19.882812 2.957031 17.40625 5.855469 16.429688 C 6.820312 16.105469 7.0625 16.078125 9.09375 16.074219 C 11.550781 16.066406 11.707031 16.109375 12.730469 17.054688 C 13.027344 17.324219 13.519531 17.675781 13.828125 17.835938 C 14.667969 18.265625 16.15625 19.835938 16.632812 20.789062 C 18.109375 23.734375 17.503906 26.886719 14.984375 29.429688 C 13.183594 31.242188 11.554688 31.890625 8.578125 31.96875 C 7.339844 32.003906 6.523438 31.960938 5.992188 31.839844 Z M 5.992188 31.839844 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -1,14 +1,10 @@
application_id = 'jp.angeltech.MoeMoji'
application_id = 'net.angeltech.MoeMoji'
scalable_dir = join_paths('hicolor', 'scalable', 'apps')
foreach size : ['48x48', '64x64', '128x128', '256x256']
size_dir = join_paths('hicolor', size, 'apps')
install_data(
join_paths(size_dir, ('@0@.png').format(application_id)),
install_dir: join_paths(get_option('datadir'), 'icons', size_dir)
)
endforeach
install_data(
join_paths(scalable_dir, ('@0@.svg').format(application_id)),
install_dir: join_paths(get_option('datadir'), 'icons', scalable_dir)
)
symbolic_dir = join_paths('hicolor', 'symbolic', 'apps')
install_data(

View File

@ -1,44 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>jp.angeltech.MoeMoji</id>
<name>MoeMoji</name>
<summary>Kaomoji picker! Japanese emoticons library</summary>
<developer id="jp.angeltech">
<name>angeltech.jp</name>
</developer>
<description>
<p>Kaomoji picker! Browse a library of Japanese emoticons, click to copy, add your own!</p>
</description>
<metadata_license>CC0-1.0</metadata_license>
<url type="homepage">https://gitea.angeltech.jp/Angel-Technologies/MoeMoji</url>
<url type="vcs-browser">https://gitea.angeltech.jp/Angel-Technologies/MoeMoji</url>
<launchable type="desktop-id">jp.angeltech.MoeMoji.desktop</launchable>
<project_license>WTFPL</project_license>
<content_rating type="oars-1.1" />
<branding>
<color type="primary" scheme_preference="light">#a1bedd</color>
<color type="primary" scheme_preference="dark">#363e4f</color>
</branding>
<screenshots>
<screenshot type="default">
<image>https://gitea.angeltech.jp/Angel-Technologies/MoeMoji/raw/commit/62eacf7e0446c95c9e075145f3a6ce42ca39d55f/flathub-preview.png</image>
<caption>Kaomoji picker</caption>
</screenshot>
</screenshots>
<releases>
<release version="0.1.0" date="2026-03-01">
<description>
<p>Initial release.</p>
</description>
</release>
</releases>
</component>

View File

@ -1,6 +1,6 @@
desktop_file = i18n.merge_file(
input: 'jp.angeltech.MoeMoji.desktop.in',
output: 'jp.angeltech.MoeMoji.desktop',
input: 'net.angeltech.MoeMoji.desktop.in',
output: 'net.angeltech.MoeMoji.desktop',
type: 'desktop',
po_dir: '../po',
install: true,
@ -15,11 +15,11 @@ if desktop_utils.found()
endif
appstream_file = i18n.merge_file(
input: 'jp.angeltech.MoeMoji.metainfo.xml.in',
output: 'jp.angeltech.MoeMoji.metainfo.xml',
input: 'net.angeltech.MoeMoji.appdata.xml.in',
output: 'net.angeltech.MoeMoji.appdata.xml',
po_dir: '../po',
install: true,
install_dir: join_paths(get_option('datadir'), 'metainfo')
install_dir: join_paths(get_option('datadir'), 'appdata')
)
appstream_util = find_program('appstream-util', required: false)
@ -29,12 +29,12 @@ if appstream_util.found()
)
endif
gnome.compile_schemas(build_by_default: true, depend_files: 'jp.angeltech.MoeMoji.gschema.xml')
gnome.compile_schemas(build_by_default: true, depend_files: 'net.angeltech.MoeMoji.gschema.xml')
devenv = environment()
devenv.set('GSETTINGS_SCHEMA_DIR', meson.current_build_dir() / 'data')
meson.add_devenv(devenv)
install_data('jp.angeltech.MoeMoji.gschema.xml',
install_data('net.angeltech.MoeMoji.gschema.xml',
install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas')
)

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>net.angeltech.MoeMoji</id>
<name>MoeMoji</name>
<summary>Moe picker!</summary>
<description>
<p>Kaomoji picker. Browse a library of Japanese emoticons, click to copy, add your own!</p>
</description>
<metadata_license>CC0-1.0</metadata_license>
<launchable type="desktop-id">net.angeltech.MoeMoji.desktop</launchable>
<project_license>WTFPL</project_license>
<content_rating type="oars-1.1" />
</component>

View File

@ -2,7 +2,7 @@
Type=Application
Name=MoeMoji
Comment=Japanese emoticon picker!
Icon=jp.angeltech.MoeMoji
Icon=net.angeltech.MoeMoji
Exec=moemoji
Terminal=false
Categories=Accessibility;GTK;Utility;

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<schemalist gettext-domain="moemoji">
<schema id='jp.angeltech.MoeMoji' path='/jp/angeltech/MoeMoji/'>
<schema id='net.angeltech.MoeMoji' path='/net/angeltech/MoeMoji/'>
</schema>
</schemalist>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 321 KiB

View File

@ -1,3 +0,0 @@
{
"only-arches": ["x86_64"]
}

View File

@ -1,26 +0,0 @@
{
"app-id": "jp.angeltech.MoeMoji",
"runtime": "org.gnome.Platform",
"runtime-version": "49",
"sdk": "org.gnome.Sdk",
"command": "moemoji",
"finish-args": [
"--share=ipc",
"--socket=fallback-x11",
"--socket=wayland",
"--talk-name=org.kde.StatusNotifierWatcher"
],
"modules": [
{
"name": "moemoji",
"buildsystem": "meson",
"sources": [
{
"type": "archive",
"url": "https://gitea.angeltech.jp/Angel-Technologies/MoeMoji/archive/v0.1.5.tar.gz",
"sha256": "76e1821051b45625a380f9b079c1be83531fa5d509e8eee5b3f41fa98dc57233"
}
]
}
]
}

View File

@ -29,9 +29,6 @@ subdir('data')
subdir('src')
subdir('tests')
install_data('LICENSE',
install_dir: join_paths(get_option('datadir'), 'licenses', 'jp.angeltech.MoeMoji')
)
gnome.post_install(
glib_compile_schemas: true,

View File

@ -1,5 +1,5 @@
{
"app-id": "jp.angeltech.MoeMoji",
"app-id": "net.angeltech.MoeMoji",
"runtime": "org.gnome.Platform",
"runtime-version": "49",
"sdk": "org.gnome.Sdk",
@ -8,7 +8,9 @@
"--share=ipc",
"--socket=fallback-x11",
"--socket=wayland",
"--talk-name=org.kde.StatusNotifierWatcher"
"--talk-name=org.kde.StatusNotifierWatcher",
"--talk-name=org.freedesktop.portal.Desktop",
"--own-name=org.kde.*"
],
"modules": [
{

Binary file not shown.

Before

Width:  |  Height:  |  Size: 335 KiB

After

Width:  |  Height:  |  Size: 250 KiB

View File

@ -9,7 +9,7 @@ static void register_with_portal (void) {
GDBusConnection *bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
if (bus == NULL) {
g_warning ("portal register: can't get session bus: %s", error->message);
g_clear_error (&error);
g_error_free (error);
return;
}
GVariantBuilder options;
@ -21,14 +21,14 @@ static void register_with_portal (void) {
"org.freedesktop.host.portal.Registry",
"Register",
g_variant_new ("(s@a{sv})",
"jp.angeltech.MoeMoji",
"net.angeltech.MoeMoji",
g_variant_builder_end (&options)),
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1, NULL, &error);
if (result == NULL) {
g_info ("portal register: %s", error->message);
g_clear_error (&error);
g_error_free (error);
} else {
g_variant_unref (result);
}
@ -41,6 +41,6 @@ int main (int argc, char *argv[]) {
textdomain(GETTEXT_PACKAGE);
register_with_portal ();
g_autoptr(MoeMojiApplication) app =
moemoji_application_new("jp.angeltech.MoeMoji", G_APPLICATION_DEFAULT_FLAGS);
moemoji_application_new("net.angeltech.MoeMoji", G_APPLICATION_DEFAULT_FLAGS);
return g_application_run(G_APPLICATION(app), argc, argv);
}

View File

@ -94,12 +94,9 @@ static void toggle_window(MoeMojiApplication *self) {
}
static gboolean on_close_request(GtkWindow *window, gpointer user_data) {
MoeMojiApplication *self = MOEMOJI_APPLICATION(user_data);
if (self->has_tray) {
gtk_widget_set_visible(GTK_WIDGET(window), FALSE);
return TRUE;
}
return FALSE;
(void)user_data;
gtk_widget_set_visible(GTK_WIDGET(window), FALSE);
return TRUE;
}
static void dbusmenu_method_call(G_GNUC_UNUSED GDBusConnection *connection,
@ -249,7 +246,7 @@ GVariant *sni_get_property(G_GNUC_UNUSED GDBusConnection *connection,
MoeMojiApplication *self = MOEMOJI_APPLICATION(user_data);
return g_variant_new_string(self->tray_icon_name
? self->tray_icon_name
: "jp.angeltech.MoeMoji-tray-dark");
: "net.angeltech.MoeMoji-tray-dark");
}
if (g_strcmp0(property_name, "ItemIsMenu") == 0)
return g_variant_new_boolean(FALSE);
@ -269,11 +266,21 @@ static const GDBusInterfaceVTable sni_vtable = {
.set_property = NULL,
};
static void on_sni_bus_name_acquired(GDBusConnection *connection,
const gchar *name,
G_GNUC_UNUSED gpointer user_data) {
g_dbus_connection_call(
connection, "org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher",
"org.kde.StatusNotifierWatcher", "RegisterStatusNotifierItem",
g_variant_new("(s)", name), NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL,
NULL);
}
static void update_tray_icon(MoeMojiApplication *self) {
AdwStyleManager *sm = adw_style_manager_get_default();
gboolean dark = adw_style_manager_get_dark(sm);
self->tray_icon_name = dark ? "jp.angeltech.MoeMoji-tray-dark"
: "jp.angeltech.MoeMoji-tray-light";
self->tray_icon_name = dark ? "net.angeltech.MoeMoji-tray-dark"
: "net.angeltech.MoeMoji-tray-light";
if (self->dbus_conn && self->sni_registration_id > 0) {
g_dbus_connection_emit_signal(self->dbus_conn, NULL, "/StatusNotifierItem",
@ -294,38 +301,13 @@ static void setup_sni(MoeMojiApplication *self) {
if (self->dbus_conn == NULL) {
g_warning("session bus: %s", error->message);
g_clear_error(&error);
self->has_tray = FALSE;
return;
}
GVariant *result = g_dbus_connection_call_sync(
self->dbus_conn, "org.freedesktop.DBus", "/org/freedesktop/DBus",
"org.freedesktop.DBus", "NameHasOwner",
g_variant_new("(s)", "org.kde.StatusNotifierWatcher"), G_VARIANT_TYPE("(b)"),
G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
if (result == NULL) {
g_warning("NameHasOwner check failed: %s", error->message);
g_clear_error(&error);
self->has_tray = FALSE;
return;
}
gboolean watcher_exists = FALSE;
g_variant_get(result, "(b)", &watcher_exists);
g_variant_unref(result);
if (!watcher_exists) {
g_info("No StatusNotifierWatcher found, skipping tray setup");
self->has_tray = FALSE;
return;
}
self->has_tray = TRUE;
GDBusNodeInfo *sni_node =
g_dbus_node_info_new_for_xml(sni_introspection_xml, &error);
if (sni_node == NULL) {
g_warning("Failed to parse SNI introspection XML: %s", error->message);
g_clear_error(&error);
g_error_free(error);
return;
}
self->sni_registration_id = g_dbus_connection_register_object(
@ -334,14 +316,14 @@ static void setup_sni(MoeMojiApplication *self) {
g_dbus_node_info_unref(sni_node);
if (self->sni_registration_id == 0) {
g_warning("Failed to register SNI object: %s", error->message);
g_clear_error(&error);
g_error_free(error);
return;
}
GDBusNodeInfo *menu_node =
g_dbus_node_info_new_for_xml(dbusmenu_introspection_xml, &error);
if (menu_node == NULL) {
g_warning("Failed to parse dbusmenu introspection XML: %s", error->message);
g_clear_error(&error);
g_error_free(error);
return;
}
self->menu_registration_id = g_dbus_connection_register_object(
@ -350,20 +332,20 @@ static void setup_sni(MoeMojiApplication *self) {
g_dbus_node_info_unref(menu_node);
if (self->menu_registration_id == 0) {
g_warning("dbusmenu register: %s", error->message);
g_clear_error(&error);
g_error_free(error);
}
const gchar *unique_name = g_dbus_connection_get_unique_name(self->dbus_conn);
g_dbus_connection_call(
self->dbus_conn, "org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher",
"org.kde.StatusNotifierWatcher", "RegisterStatusNotifierItem",
g_variant_new("(s)", unique_name), NULL, G_DBUS_CALL_FLAGS_NONE, -1,
NULL, NULL, NULL);
g_autofree gchar *bus_name =
g_strdup_printf("org.kde.StatusNotifierItem-%d-1", getpid());
self->sni_bus_name_id = g_bus_own_name_on_connection(
self->dbus_conn, bus_name, G_BUS_NAME_OWNER_FLAGS_NONE,
on_sni_bus_name_acquired, NULL, NULL, NULL);
}
static void on_shortcuts_activated(G_GNUC_UNUSED GDBusProxy *proxy,
G_GNUC_UNUSED const gchar *sender_name,
static void on_shortcuts_activated(GDBusProxy *proxy, const gchar *sender_name,
const gchar *signal_name,
GVariant *parameters, gpointer user_data) {
(void)proxy;
(void)sender_name;
if (g_strcmp0(signal_name, "Activated") != 0)
return;
MoeMojiApplication *self = MOEMOJI_APPLICATION(user_data);
@ -508,7 +490,7 @@ static void setup_global_shortcuts(MoeMojiApplication *self) {
"org.freedesktop.portal.GlobalShortcuts", NULL, &error);
if (self->shortcuts_proxy == NULL) {
g_warning("GlobalShortcuts: Failed to create proxy: %s", error->message);
g_clear_error(&error);
g_error_free(error);
return;
}
g_timeout_add(500, (GSourceFunc)begin_create_session, self);
@ -525,8 +507,8 @@ static void moemoji_application_startup(GApplication *app) {
MoeMojiApplication *self = MOEMOJI_APPLICATION(app);
gtk_icon_theme_add_resource_path(
gtk_icon_theme_get_for_display(gdk_display_get_default()),
"/jp/angeltech/MoeMoji/icons");
gtk_window_set_default_icon_name("jp.angeltech.MoeMoji");
"/net/angeltech/MoeMoji/icons");
gtk_window_set_default_icon_name("net.angeltech.MoeMoji-symbolic");
gboolean in_flatpak = g_file_test("/.flatpak-info", G_FILE_TEST_EXISTS);
if (in_flatpak) {
self->icon_theme_path = NULL;
@ -560,7 +542,7 @@ static void moemoji_application_activate(GApplication *app) {
self->window = g_object_new(MOEMOJI_TYPE_WINDOW, "application",
GTK_APPLICATION(self), NULL);
g_signal_connect(self->window, "close-request",
G_CALLBACK(on_close_request), self);
G_CALLBACK(on_close_request), NULL);
self->window_created = TRUE;
} else {
toggle_window(self);
@ -579,6 +561,10 @@ static void moemoji_application_dispose(GObject *object) {
self->menu_registration_id);
self->menu_registration_id = 0;
}
if (self->sni_bus_name_id > 0) {
g_bus_unown_name(self->sni_bus_name_id);
self->sni_bus_name_id = 0;
}
g_clear_object(&self->shortcuts_proxy);
g_clear_pointer(&self->shortcuts_session_path, g_free);
g_clear_pointer(&self->icon_theme_path, g_free);
@ -606,7 +592,7 @@ static void moemoji_application_class_init(MoeMojiApplicationClass *klass) {
}
static void moemoji_application_init(MoeMojiApplication *self) {
self->settings = g_settings_new("jp.angeltech.MoeMoji");
self->settings = g_settings_new("net.angeltech.MoeMoji");
self->window_created = FALSE;
self->shortcuts_bound = FALSE;
g_autoptr(GSimpleAction) quit_action = g_simple_action_new("quit", NULL);

View File

@ -11,12 +11,12 @@ struct _MoeMojiApplication {
gboolean shortcuts_bound;
GDBusConnection *dbus_conn;
guint sni_registration_id;
guint sni_bus_name_id;
guint menu_registration_id;
gchar *icon_theme_path;
const gchar *tray_icon_name;
GDBusProxy *shortcuts_proxy;
gchar *shortcuts_session_path;
gboolean has_tray;
};
G_BEGIN_DECLS

View File

@ -62,13 +62,6 @@ static void on_popover_leave(G_GNUC_UNUSED GtkEventControllerMotion *ctrl,
gtk_popover_popdown(GTK_POPOVER(user_data));
}
static void on_button_destroy(GtkWidget *button,
G_GNUC_UNUSED gpointer user_data) {
GtkWidget *popover = g_object_get_data(G_OBJECT(button), "popover");
if (popover)
gtk_widget_unparent(popover);
}
static void add_kaomoji_button(GtkFlowBox *flow, const char *text) {
gboolean multiline = (strchr(text, '\n') != NULL);
const char *label_text = text;
@ -95,8 +88,6 @@ static void add_kaomoji_button(GtkFlowBox *flow, const char *text) {
gtk_widget_add_css_class(label, "kaomoji-preview");
gtk_popover_set_child(GTK_POPOVER(popover), label);
gtk_widget_set_parent(popover, button);
g_object_set_data(G_OBJECT(button), "popover", popover);
g_signal_connect(button, "destroy", G_CALLBACK(on_button_destroy), NULL);
GtkEventController *motion = gtk_event_controller_motion_new();
g_signal_connect(motion, "enter", G_CALLBACK(on_popover_enter), popover);
@ -148,127 +139,47 @@ static void load_category(MoeMojiWindow *self, const char *kaomoji_dir,
g_free(cat_path);
}
static void set_active_chip(MoeMojiWindow *self, int index) {
if (index == self->active_chip_index)
return;
if (self->active_chip_index >= 0 &&
(guint)self->active_chip_index < self->category_widgets->len) {
CategoryWidgets *old =
g_ptr_array_index(self->category_widgets, self->active_chip_index);
if (old->chip)
gtk_widget_remove_css_class(old->chip, "chip-active");
}
self->active_chip_index = index;
if (index >= 0 && (guint)index < self->category_widgets->len) {
CategoryWidgets *cw = g_ptr_array_index(self->category_widgets, index);
if (cw->chip)
gtk_widget_add_css_class(cw->chip, "chip-active");
}
}
static void scroll_to_category(MoeMojiWindow *self, int index) {
CategoryWidgets *cw = g_ptr_array_index(self->category_widgets, index);
graphene_point_t p;
if (gtk_widget_compute_point(cw->header, GTK_WIDGET(self->content_box),
&GRAPHENE_POINT_INIT(0, 0), &p)) {
GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(
GTK_SCROLLED_WINDOW(self->kaomoji_scroll));
gtk_adjustment_set_value(vadj, p.y);
}
}
static void on_chip_clicked(GtkButton *button, gpointer user_data) {
MoeMojiWindow *self = MOEMOJI_WINDOW(user_data);
for (guint i = 0; i < self->category_widgets->len; i++) {
CategoryWidgets *cw = g_ptr_array_index(self->category_widgets, i);
if (cw->chip == GTK_WIDGET(button)) {
scroll_to_category(self, (int)i);
return;
}
}
}
static void update_chip_from_scroll(MoeMojiWindow *self) {
double scroll_pos = gtk_adjustment_get_value(
gtk_scrolled_window_get_vadjustment(
GTK_SCROLLED_WINDOW(self->kaomoji_scroll)));
int best = 0;
for (guint i = 0; i < self->category_widgets->len; i++) {
CategoryWidgets *cw = g_ptr_array_index(self->category_widgets, i);
graphene_point_t p;
if (gtk_widget_compute_point(cw->header, GTK_WIDGET(self->content_box),
&GRAPHENE_POINT_INIT(0, 0), &p)) {
if (p.y <= scroll_pos)
best = (int)i;
}
}
set_active_chip(self, best);
}
static void update_spacer_height(MoeMojiWindow *self) {
if (!self->bottom_spacer || self->category_widgets->len == 0)
return;
GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(
GTK_SCROLLED_WINDOW(self->kaomoji_scroll));
int page = (int)gtk_adjustment_get_page_size(vadj);
CategoryWidgets *last = g_ptr_array_index(self->category_widgets,
self->category_widgets->len - 1);
int spacing = gtk_box_get_spacing(self->content_box);
int last_h = gtk_widget_get_height(last->header) + spacing +
gtk_widget_get_height(last->flow);
int spacer_h = page - last_h;
if (spacer_h < 0)
spacer_h = 0;
gtk_widget_set_size_request(self->bottom_spacer, -1, spacer_h);
}
static void on_scroll_changed(G_GNUC_UNUSED GtkAdjustment *adj,
gpointer user_data) {
MoeMojiWindow *self = MOEMOJI_WINDOW(user_data);
update_chip_from_scroll(self);
}
static void on_page_size_changed(G_GNUC_UNUSED GtkAdjustment *adj,
G_GNUC_UNUSED GParamSpec *pspec,
gpointer user_data) {
MoeMojiWindow *self = MOEMOJI_WINDOW(user_data);
update_spacer_height(self);
}
static void on_kaomoji_scroll_map(G_GNUC_UNUSED GtkWidget *widget,
gpointer user_data) {
MoeMojiWindow *self = MOEMOJI_WINDOW(user_data);
update_chip_from_scroll(self);
const char *current = gtk_editable_get_text(GTK_EDITABLE(self->search_entry));
const char *name = gtk_button_get_label(button);
if (g_strcmp0(current, name) == 0)
gtk_editable_set_text(GTK_EDITABLE(self->search_entry), "");
else
gtk_editable_set_text(GTK_EDITABLE(self->search_entry), name);
}
static void on_search_changed(GtkSearchEntry *entry, gpointer user_data) {
MoeMojiWindow *self = MOEMOJI_WINDOW(user_data);
const char *query = gtk_editable_get_text(GTK_EDITABLE(entry));
if (query == NULL || query[0] == '\0')
return;
g_autofree char *query_lower = g_utf8_strdown(query, -1);
for (guint i = 0; i < self->category_widgets->len; i++) {
CategoryWidgets *cw = g_ptr_array_index(self->category_widgets, i);
g_autofree char *name_lower = g_utf8_strdown(cw->name, -1);
if (strstr(name_lower, query_lower) != NULL) {
scroll_to_category(self, (int)i);
return;
gboolean visible;
if (query == NULL || query[0] == '\0') {
visible = TRUE;
} else {
char *name_lower = g_utf8_strdown(cw->name, -1);
char *query_lower = g_utf8_strdown(query, -1);
visible = (strstr(name_lower, query_lower) != NULL);
g_free(name_lower);
g_free(query_lower);
}
gtk_widget_set_visible(cw->header, visible);
gtk_widget_set_visible(cw->flow, visible);
}
}
static void moemoji_window_class_init(MoeMojiWindowClass *klass) {
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
gtk_widget_class_set_template_from_resource(
widget_class, "/jp/angeltech/MoeMoji/moemoji-window.ui");
widget_class, "/net/angeltech/MoeMoji/moemoji-window.ui");
gtk_widget_class_bind_template_child(widget_class, MoeMojiWindow, outer_box);
gtk_widget_class_bind_template_child(widget_class, MoeMojiWindow,
content_box);
gtk_widget_class_bind_template_child(widget_class, MoeMojiWindow,
search_entry);
gtk_widget_class_bind_template_child(widget_class, MoeMojiWindow, header_bar);
gtk_widget_class_bind_template_child(widget_class, MoeMojiWindow,
kaomoji_scroll);
}
static void collect_categories(const char *base_dir, GHashTable *seen,
@ -307,7 +218,6 @@ static void moemoji_window_init(MoeMojiWindow *self) {
gtk_widget_add_css_class(GTK_WIDGET(self->content_box), "content-area");
self->category_widgets =
g_ptr_array_new_with_free_func(category_widgets_free);
self->active_chip_index = -1;
char *kaomoji_dir = find_kaomoji_dir();
g_autofree char *user_dir =
g_build_filename(g_get_user_data_dir(), "moemoji", "kaomoji", NULL);
@ -336,8 +246,6 @@ static void moemoji_window_init(MoeMojiWindow *self) {
}
g_ptr_array_free(entries, TRUE);
g_free(kaomoji_dir);
self->bottom_spacer = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_box_append(self->content_box, self->bottom_spacer);
g_signal_connect(self->search_entry, "search-changed",
G_CALLBACK(on_search_changed), self);
if (self->category_widgets->len > 0) {
@ -352,7 +260,6 @@ static void moemoji_window_init(MoeMojiWindow *self) {
gtk_widget_add_css_class(btn, "category-chip");
gtk_widget_add_css_class(btn, "flat");
g_signal_connect(btn, "clicked", G_CALLBACK(on_chip_clicked), self);
cw->chip = btn;
gtk_flow_box_insert(GTK_FLOW_BOX(flow), btn, -1);
}
self->category_bar = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
@ -368,14 +275,5 @@ static void moemoji_window_init(MoeMojiWindow *self) {
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(cat_scroll), flow);
gtk_box_append(self->category_bar, cat_scroll);
gtk_box_append(self->outer_box, GTK_WIDGET(self->category_bar));
GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(
GTK_SCROLLED_WINDOW(self->kaomoji_scroll));
g_signal_connect(vadj, "value-changed", G_CALLBACK(on_scroll_changed),
self);
g_signal_connect(vadj, "notify::page-size",
G_CALLBACK(on_page_size_changed), self);
g_signal_connect(self->kaomoji_scroll, "map",
G_CALLBACK(on_kaomoji_scroll_map), self);
}
}

View File

@ -5,7 +5,6 @@
typedef struct {
GtkWidget *header;
GtkWidget *flow;
GtkWidget *chip;
char *name;
} CategoryWidgets;
@ -16,10 +15,7 @@ struct _MoeMojiWindow {
GtkSearchEntry *search_entry;
GtkWidget *header_bar;
GtkBox *category_bar;
GtkWidget *kaomoji_scroll;
GPtrArray *category_widgets;
GtkWidget *bottom_spacer;
int active_chip_index;
};
G_BEGIN_DECLS

View File

@ -2,8 +2,8 @@
<interface>
<requires lib="gtk" version="4.0" />
<template class="MoeMojiWindow" parent="GtkApplicationWindow">
<property name="default-width">680</property>
<property name="default-height">400</property>
<property name="default-width">280</property>
<property name="default-height">600</property>
<property name="resizable">True</property>
<property name="title">MoeMoji</property>
@ -18,14 +18,14 @@
<property name="orientation">vertical</property>
<child>
<object class="GtkSearchEntry" id="search_entry">
<property name="placeholder-text">Jump to category...</property>
<property name="placeholder-text">Filter by category...</property>
<property name="margin-start">6</property>
<property name="margin-end">6</property>
<property name="margin-top">6</property>
</object>
</child>
<child>
<object class="GtkScrolledWindow" id="kaomoji_scroll">
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<property name="vexpand">True</property>
<child>

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/jp/angeltech/MoeMoji">
<gresource prefix="/net/angeltech/MoeMoji">
<file>moemoji-window.ui</file>
<file>style.css</file>
<file>wp.png</file>
<file alias="icons/scalable/apps/jp.angeltech.MoeMoji.png">../data/icons/hicolor/scalable/apps/jp.angeltech.MoeMoji.png</file>
<file alias="icons/scalable/apps/jp.angeltech.MoeMoji-symbolic.svg">../data/icons/hicolor/symbolic/apps/jp.angeltech.MoeMoji-symbolic.svg</file>
<file alias="icons/scalable/apps/net.angeltech.MoeMoji.svg">../data/icons/hicolor/scalable/apps/net.angeltech.MoeMoji.svg</file>
<file alias="icons/scalable/apps/net.angeltech.MoeMoji-symbolic.svg">../data/icons/hicolor/symbolic/apps/net.angeltech.MoeMoji-symbolic.svg</file>
</gresource>
</gresources>
</gresources>

View File

@ -31,10 +31,9 @@
}
.wallpaper-bg {
background-image: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url("/jp/angeltech/MoeMoji/wp.png");
background-image: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url("/net/angeltech/MoeMoji/wp.png");
background-size: cover;
background-color: transparent;
color: white;
}
wallpaper-bg > * {
@ -54,8 +53,3 @@ wallpaper-bg > * {
border-radius: 14px;
padding: 8px;
}
.chip-active {
background-color: rgba(255, 255, 255, 0.25);
border-radius: 8px;
}

View File

@ -1,8 +1,6 @@
#include <glib.h>
#include <glib/gstdio.h>
#include <gtk/gtk.h>
#include "moemoji-internal.h"
#include "moemoji-window.h"
static void
test_display_name_underscores (void)
@ -47,16 +45,16 @@ test_display_name_empty (void)
static void
test_find_kaomoji_with_env (void)
{
g_setenv ("MESON_SOURCE_ROOT", SRCDIR, TRUE);
char *dir = find_kaomoji_dir ();
g_assert_nonnull (dir);
g_assert_true (g_file_test (dir, G_FILE_TEST_IS_DIR));
g_free (dir);
g_unsetenv ("MESON_SOURCE_ROOT");
}
static gboolean find_kaomoji_bogus_passed = FALSE;
static void
run_find_kaomoji_bogus (void)
test_find_kaomoji_bogus (void)
{
g_setenv ("MESON_SOURCE_ROOT", "/nonexistent", TRUE);
char *saved = g_get_current_dir ();
@ -67,14 +65,7 @@ run_find_kaomoji_bogus (void)
g_assert_true (g_chdir (saved) == 0);
g_free (saved);
g_setenv ("MESON_SOURCE_ROOT", SRCDIR, TRUE);
find_kaomoji_bogus_passed = TRUE;
}
static void
test_find_kaomoji_bogus (void)
{
g_assert_true (find_kaomoji_bogus_passed);
g_unsetenv ("MESON_SOURCE_ROOT");
}
static void
@ -154,141 +145,27 @@ test_dbusmenu_unknown (void)
g_assert_null (v);
}
static void
test_category_widgets_chip_init (void)
{
CategoryWidgets *cw = g_new0 (CategoryWidgets, 1);
g_assert_null (cw->chip);
g_assert_null (cw->header);
g_assert_null (cw->flow);
g_assert_null (cw->name);
g_free (cw);
}
G_GNUC_INTERNAL GResource *moemoji_get_resource (void);
static GResource *test_res = NULL;
static GtkWidget *test_win = NULL;
static MoeMojiWindow *test_self = NULL;
static void
window_test_setup (void)
{
if (!test_res) {
test_res = moemoji_get_resource ();
g_resources_register (test_res);
}
test_win = g_object_new (MOEMOJI_TYPE_WINDOW, NULL);
test_self = MOEMOJI_WINDOW (test_win);
}
static void
window_test_teardown (void)
{
gtk_window_destroy (GTK_WINDOW (test_win));
test_win = NULL;
test_self = NULL;
}
static void
test_window_chip_setup (void)
{
window_test_setup ();
g_assert_cmpuint (test_self->category_widgets->len, >, 0);
for (guint i = 0; i < test_self->category_widgets->len; i++) {
CategoryWidgets *cw = g_ptr_array_index (test_self->category_widgets, i);
g_assert_nonnull (cw->chip);
}
window_test_teardown ();
}
static void
test_window_chip_labels_match (void)
{
window_test_setup ();
for (guint i = 0; i < test_self->category_widgets->len; i++) {
CategoryWidgets *cw = g_ptr_array_index (test_self->category_widgets, i);
const char *chip_label = gtk_button_get_label (GTK_BUTTON (cw->chip));
g_assert_cmpstr (chip_label, ==, cw->name);
}
window_test_teardown ();
}
static void
test_window_initial_active_chip (void)
{
window_test_setup ();
g_assert_cmpint (test_self->active_chip_index, ==, -1);
window_test_teardown ();
}
static void
test_window_all_categories_visible (void)
{
window_test_setup ();
for (guint i = 0; i < test_self->category_widgets->len; i++) {
CategoryWidgets *cw = g_ptr_array_index (test_self->category_widgets, i);
g_assert_true (gtk_widget_get_visible (cw->header));
g_assert_true (gtk_widget_get_visible (cw->flow));
}
window_test_teardown ();
}
static void
test_window_bottom_spacer (void)
{
window_test_setup ();
g_assert_nonnull (test_self->bottom_spacer);
GtkWidget *last = gtk_widget_get_last_child (GTK_WIDGET (test_self->content_box));
g_assert_true (last == test_self->bottom_spacer);
window_test_teardown ();
}
int
main (int argc, char *argv[])
{
g_setenv ("MESON_SOURCE_ROOT", SRCDIR, TRUE);
g_test_init (&argc, &argv, NULL);
run_find_kaomoji_bogus ();
gboolean have_display = gtk_init_check ();
g_test_add_func ("/display-name/underscores", test_display_name_underscores);
g_test_add_func ("/display-name/no-underscores", test_display_name_no_underscores);
g_test_add_func ("/display-name/already-upper", test_display_name_already_upper);
g_test_add_func ("/display-name/single-char", test_display_name_single_char);
g_test_add_func ("/display-name/empty", test_display_name_empty);
g_test_add_func ("/find-kaomoji/with-env", test_find_kaomoji_with_env);
g_test_add_func ("/find-kaomoji/bogus", test_find_kaomoji_bogus);
g_test_add_func ("/sni/category", test_sni_category);
g_test_add_func ("/sni/id", test_sni_id);
g_test_add_func ("/sni/item-is-menu", test_sni_item_is_menu);
g_test_add_func ("/sni/menu", test_sni_menu);
g_test_add_func ("/sni/unknown", test_sni_unknown);
g_test_add_func ("/dbusmenu/version", test_dbusmenu_version);
g_test_add_func ("/dbusmenu/status", test_dbusmenu_status);
g_test_add_func ("/dbusmenu/text-direction", test_dbusmenu_text_direction);
g_test_add_func ("/dbusmenu/unknown", test_dbusmenu_unknown);
g_test_add_func ("/category-widgets/chip-init", test_category_widgets_chip_init);
if (have_display) {
g_test_add_func ("/window/chip-setup", test_window_chip_setup);
g_test_add_func ("/window/chip-labels-match", test_window_chip_labels_match);
g_test_add_func ("/window/initial-active-chip", test_window_initial_active_chip);
g_test_add_func ("/window/all-categories-visible", test_window_all_categories_visible);
g_test_add_func ("/window/bottom-spacer", test_window_bottom_spacer);
}
g_test_add_func ("/display-name/underscores", test_display_name_underscores);
g_test_add_func ("/display-name/no-underscores", test_display_name_no_underscores);
g_test_add_func ("/display-name/already-upper", test_display_name_already_upper);
g_test_add_func ("/display-name/single-char", test_display_name_single_char);
g_test_add_func ("/display-name/empty", test_display_name_empty);
g_test_add_func ("/find-kaomoji/with-env", test_find_kaomoji_with_env);
g_test_add_func ("/find-kaomoji/bogus", test_find_kaomoji_bogus);
g_test_add_func ("/sni/category", test_sni_category);
g_test_add_func ("/sni/id", test_sni_id);
g_test_add_func ("/sni/item-is-menu", test_sni_item_is_menu);
g_test_add_func ("/sni/menu", test_sni_menu);
g_test_add_func ("/sni/unknown", test_sni_unknown);
g_test_add_func ("/dbusmenu/version", test_dbusmenu_version);
g_test_add_func ("/dbusmenu/status", test_dbusmenu_status);
g_test_add_func ("/dbusmenu/text-direction", test_dbusmenu_text_direction);
g_test_add_func ("/dbusmenu/unknown", test_dbusmenu_unknown);
return g_test_run ();
}