Compare commits
@@ -3,10 +3,16 @@ Kaomoji picker. Browse a library of Japanese emoticons, click to copy, add your
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## Install
|
||||||
|
1. Download .flatpak file from Releases: https://gitea.angeltech.jp/Angel-Technologies/MoeMoji/releases/tag/konater
|
||||||
|
2. Install with `flatpak install ./moemoji.flatpak`
|
||||||
|
|
||||||
## Global shortcut
|
## 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.
|
This app uses global shortcut (Meta+Shift+E) for easy access. Press once to show the window, press twice to hide the window.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -25,6 +31,15 @@ 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`.
|
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.
|
You can add large ASCII text files or any text you want; the app handles multiline texts and displays a neat little preview.
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
@@ -36,5 +51,5 @@ Run dev build: `GSETTINGS_SCHEMA_DIR=./builddir/data ./builddir/src/moemoji`
|
|||||||
|
|
||||||
# Flatpak packaging
|
# Flatpak packaging
|
||||||
|
|
||||||
`flatpak-builder --force-clean --user --install .flatpak-build net.angeltech.MoeMoji.json`
|
`flatpak-builder --force-clean --user --install .flatpak-build jp.angeltech.MoeMoji.json`
|
||||||
`flatpak run net.angeltech.MoeMoji`
|
`flatpak run jp.angeltech.MoeMoji`
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
@@ -1,4 +1,4 @@
|
|||||||
application_id = 'net.angeltech.MoeMoji'
|
application_id = 'jp.angeltech.MoeMoji'
|
||||||
|
|
||||||
scalable_dir = join_paths('hicolor', 'scalable', 'apps')
|
scalable_dir = join_paths('hicolor', 'scalable', 'apps')
|
||||||
install_data(
|
install_data(
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
Type=Application
|
Type=Application
|
||||||
Name=MoeMoji
|
Name=MoeMoji
|
||||||
Comment=Japanese emoticon picker!
|
Comment=Japanese emoticon picker!
|
||||||
Icon=net.angeltech.MoeMoji
|
Icon=jp.angeltech.MoeMoji
|
||||||
Exec=moemoji
|
Exec=moemoji
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=Accessibility;GTK;Utility;
|
Categories=Accessibility;GTK;Utility;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<schemalist gettext-domain="moemoji">
|
<schemalist gettext-domain="moemoji">
|
||||||
<schema id='net.angeltech.MoeMoji' path='/net/angeltech/MoeMoji/'>
|
<schema id='jp.angeltech.MoeMoji' path='/jp/angeltech/MoeMoji/'>
|
||||||
</schema>
|
</schema>
|
||||||
</schemalist>
|
</schemalist>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?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>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
desktop_file = i18n.merge_file(
|
desktop_file = i18n.merge_file(
|
||||||
input: 'net.angeltech.MoeMoji.desktop.in',
|
input: 'jp.angeltech.MoeMoji.desktop.in',
|
||||||
output: 'net.angeltech.MoeMoji.desktop',
|
output: 'jp.angeltech.MoeMoji.desktop',
|
||||||
type: 'desktop',
|
type: 'desktop',
|
||||||
po_dir: '../po',
|
po_dir: '../po',
|
||||||
install: true,
|
install: true,
|
||||||
@@ -15,11 +15,11 @@ if desktop_utils.found()
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
appstream_file = i18n.merge_file(
|
appstream_file = i18n.merge_file(
|
||||||
input: 'net.angeltech.MoeMoji.appdata.xml.in',
|
input: 'jp.angeltech.MoeMoji.metainfo.xml.in',
|
||||||
output: 'net.angeltech.MoeMoji.appdata.xml',
|
output: 'jp.angeltech.MoeMoji.metainfo.xml',
|
||||||
po_dir: '../po',
|
po_dir: '../po',
|
||||||
install: true,
|
install: true,
|
||||||
install_dir: join_paths(get_option('datadir'), 'appdata')
|
install_dir: join_paths(get_option('datadir'), 'metainfo')
|
||||||
)
|
)
|
||||||
|
|
||||||
appstream_util = find_program('appstream-util', required: false)
|
appstream_util = find_program('appstream-util', required: false)
|
||||||
@@ -29,12 +29,12 @@ if appstream_util.found()
|
|||||||
)
|
)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
gnome.compile_schemas(build_by_default: true, depend_files: 'net.angeltech.MoeMoji.gschema.xml')
|
gnome.compile_schemas(build_by_default: true, depend_files: 'jp.angeltech.MoeMoji.gschema.xml')
|
||||||
devenv = environment()
|
devenv = environment()
|
||||||
devenv.set('GSETTINGS_SCHEMA_DIR', meson.current_build_dir() / 'data')
|
devenv.set('GSETTINGS_SCHEMA_DIR', meson.current_build_dir() / 'data')
|
||||||
meson.add_devenv(devenv)
|
meson.add_devenv(devenv)
|
||||||
|
|
||||||
install_data('net.angeltech.MoeMoji.gschema.xml',
|
install_data('jp.angeltech.MoeMoji.gschema.xml',
|
||||||
install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas')
|
install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
<?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>
|
|
||||||
|
After Width: | Height: | Size: 321 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"only-arches": ["x86_64"]
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"app-id": "net.angeltech.MoeMoji",
|
"app-id": "jp.angeltech.MoeMoji",
|
||||||
"runtime": "org.gnome.Platform",
|
"runtime": "org.gnome.Platform",
|
||||||
"runtime-version": "49",
|
"runtime-version": "49",
|
||||||
"sdk": "org.gnome.Sdk",
|
"sdk": "org.gnome.Sdk",
|
||||||
@@ -8,9 +8,7 @@
|
|||||||
"--share=ipc",
|
"--share=ipc",
|
||||||
"--socket=fallback-x11",
|
"--socket=fallback-x11",
|
||||||
"--socket=wayland",
|
"--socket=wayland",
|
||||||
"--talk-name=org.kde.StatusNotifierWatcher",
|
"--talk-name=org.kde.StatusNotifierWatcher"
|
||||||
"--talk-name=org.freedesktop.portal.Desktop",
|
|
||||||
"--own-name=org.kde.*"
|
|
||||||
],
|
],
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
{
|
||||||
@@ -18,8 +16,9 @@
|
|||||||
"buildsystem": "meson",
|
"buildsystem": "meson",
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"type": "dir",
|
"type": "archive",
|
||||||
"path": "."
|
"url": "https://gitea.angeltech.jp/Angel-Technologies/MoeMoji/archive/v0.1.2.tar.gz",
|
||||||
|
"sha256": "01fd51050f723fc14ba49f8dc09fb5e67336c7e9a54f0c39cb16c69930b26358"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -29,6 +29,9 @@ subdir('data')
|
|||||||
subdir('src')
|
subdir('src')
|
||||||
subdir('tests')
|
subdir('tests')
|
||||||
|
|
||||||
|
install_data('LICENSE',
|
||||||
|
install_dir: join_paths(get_option('datadir'), 'licenses', 'jp.angeltech.MoeMoji')
|
||||||
|
)
|
||||||
|
|
||||||
gnome.post_install(
|
gnome.post_install(
|
||||||
glib_compile_schemas: true,
|
glib_compile_schemas: true,
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 250 KiB After Width: | Height: | Size: 335 KiB |
@@ -9,7 +9,7 @@ static void register_with_portal (void) {
|
|||||||
GDBusConnection *bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
|
GDBusConnection *bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
|
||||||
if (bus == NULL) {
|
if (bus == NULL) {
|
||||||
g_warning ("portal register: can't get session bus: %s", error->message);
|
g_warning ("portal register: can't get session bus: %s", error->message);
|
||||||
g_error_free (error);
|
g_clear_error (&error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
GVariantBuilder options;
|
GVariantBuilder options;
|
||||||
@@ -21,14 +21,14 @@ static void register_with_portal (void) {
|
|||||||
"org.freedesktop.host.portal.Registry",
|
"org.freedesktop.host.portal.Registry",
|
||||||
"Register",
|
"Register",
|
||||||
g_variant_new ("(s@a{sv})",
|
g_variant_new ("(s@a{sv})",
|
||||||
"net.angeltech.MoeMoji",
|
"jp.angeltech.MoeMoji",
|
||||||
g_variant_builder_end (&options)),
|
g_variant_builder_end (&options)),
|
||||||
NULL,
|
NULL,
|
||||||
G_DBUS_CALL_FLAGS_NONE,
|
G_DBUS_CALL_FLAGS_NONE,
|
||||||
-1, NULL, &error);
|
-1, NULL, &error);
|
||||||
if (result == NULL) {
|
if (result == NULL) {
|
||||||
g_info ("portal register: %s", error->message);
|
g_info ("portal register: %s", error->message);
|
||||||
g_error_free (error);
|
g_clear_error (&error);
|
||||||
} else {
|
} else {
|
||||||
g_variant_unref (result);
|
g_variant_unref (result);
|
||||||
}
|
}
|
||||||
@@ -41,6 +41,6 @@ int main (int argc, char *argv[]) {
|
|||||||
textdomain(GETTEXT_PACKAGE);
|
textdomain(GETTEXT_PACKAGE);
|
||||||
register_with_portal ();
|
register_with_portal ();
|
||||||
g_autoptr(MoeMojiApplication) app =
|
g_autoptr(MoeMojiApplication) app =
|
||||||
moemoji_application_new("net.angeltech.MoeMoji", G_APPLICATION_DEFAULT_FLAGS);
|
moemoji_application_new("jp.angeltech.MoeMoji", G_APPLICATION_DEFAULT_FLAGS);
|
||||||
return g_application_run(G_APPLICATION(app), argc, argv);
|
return g_application_run(G_APPLICATION(app), argc, argv);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,9 +94,12 @@ static void toggle_window(MoeMojiApplication *self) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static gboolean on_close_request(GtkWindow *window, gpointer user_data) {
|
static gboolean on_close_request(GtkWindow *window, gpointer user_data) {
|
||||||
(void)user_data;
|
MoeMojiApplication *self = MOEMOJI_APPLICATION(user_data);
|
||||||
gtk_widget_set_visible(GTK_WIDGET(window), FALSE);
|
if (self->has_tray) {
|
||||||
return TRUE;
|
gtk_widget_set_visible(GTK_WIDGET(window), FALSE);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dbusmenu_method_call(G_GNUC_UNUSED GDBusConnection *connection,
|
static void dbusmenu_method_call(G_GNUC_UNUSED GDBusConnection *connection,
|
||||||
@@ -246,7 +249,7 @@ GVariant *sni_get_property(G_GNUC_UNUSED GDBusConnection *connection,
|
|||||||
MoeMojiApplication *self = MOEMOJI_APPLICATION(user_data);
|
MoeMojiApplication *self = MOEMOJI_APPLICATION(user_data);
|
||||||
return g_variant_new_string(self->tray_icon_name
|
return g_variant_new_string(self->tray_icon_name
|
||||||
? self->tray_icon_name
|
? self->tray_icon_name
|
||||||
: "net.angeltech.MoeMoji-tray-dark");
|
: "jp.angeltech.MoeMoji-tray-dark");
|
||||||
}
|
}
|
||||||
if (g_strcmp0(property_name, "ItemIsMenu") == 0)
|
if (g_strcmp0(property_name, "ItemIsMenu") == 0)
|
||||||
return g_variant_new_boolean(FALSE);
|
return g_variant_new_boolean(FALSE);
|
||||||
@@ -266,21 +269,11 @@ static const GDBusInterfaceVTable sni_vtable = {
|
|||||||
.set_property = NULL,
|
.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) {
|
static void update_tray_icon(MoeMojiApplication *self) {
|
||||||
AdwStyleManager *sm = adw_style_manager_get_default();
|
AdwStyleManager *sm = adw_style_manager_get_default();
|
||||||
gboolean dark = adw_style_manager_get_dark(sm);
|
gboolean dark = adw_style_manager_get_dark(sm);
|
||||||
self->tray_icon_name = dark ? "net.angeltech.MoeMoji-tray-dark"
|
self->tray_icon_name = dark ? "jp.angeltech.MoeMoji-tray-dark"
|
||||||
: "net.angeltech.MoeMoji-tray-light";
|
: "jp.angeltech.MoeMoji-tray-light";
|
||||||
|
|
||||||
if (self->dbus_conn && self->sni_registration_id > 0) {
|
if (self->dbus_conn && self->sni_registration_id > 0) {
|
||||||
g_dbus_connection_emit_signal(self->dbus_conn, NULL, "/StatusNotifierItem",
|
g_dbus_connection_emit_signal(self->dbus_conn, NULL, "/StatusNotifierItem",
|
||||||
@@ -301,13 +294,38 @@ static void setup_sni(MoeMojiApplication *self) {
|
|||||||
if (self->dbus_conn == NULL) {
|
if (self->dbus_conn == NULL) {
|
||||||
g_warning("session bus: %s", error->message);
|
g_warning("session bus: %s", error->message);
|
||||||
g_clear_error(&error);
|
g_clear_error(&error);
|
||||||
|
self->has_tray = FALSE;
|
||||||
return;
|
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 =
|
GDBusNodeInfo *sni_node =
|
||||||
g_dbus_node_info_new_for_xml(sni_introspection_xml, &error);
|
g_dbus_node_info_new_for_xml(sni_introspection_xml, &error);
|
||||||
if (sni_node == NULL) {
|
if (sni_node == NULL) {
|
||||||
g_warning("Failed to parse SNI introspection XML: %s", error->message);
|
g_warning("Failed to parse SNI introspection XML: %s", error->message);
|
||||||
g_error_free(error);
|
g_clear_error(&error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self->sni_registration_id = g_dbus_connection_register_object(
|
self->sni_registration_id = g_dbus_connection_register_object(
|
||||||
@@ -316,14 +334,14 @@ static void setup_sni(MoeMojiApplication *self) {
|
|||||||
g_dbus_node_info_unref(sni_node);
|
g_dbus_node_info_unref(sni_node);
|
||||||
if (self->sni_registration_id == 0) {
|
if (self->sni_registration_id == 0) {
|
||||||
g_warning("Failed to register SNI object: %s", error->message);
|
g_warning("Failed to register SNI object: %s", error->message);
|
||||||
g_error_free(error);
|
g_clear_error(&error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
GDBusNodeInfo *menu_node =
|
GDBusNodeInfo *menu_node =
|
||||||
g_dbus_node_info_new_for_xml(dbusmenu_introspection_xml, &error);
|
g_dbus_node_info_new_for_xml(dbusmenu_introspection_xml, &error);
|
||||||
if (menu_node == NULL) {
|
if (menu_node == NULL) {
|
||||||
g_warning("Failed to parse dbusmenu introspection XML: %s", error->message);
|
g_warning("Failed to parse dbusmenu introspection XML: %s", error->message);
|
||||||
g_error_free(error);
|
g_clear_error(&error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self->menu_registration_id = g_dbus_connection_register_object(
|
self->menu_registration_id = g_dbus_connection_register_object(
|
||||||
@@ -332,20 +350,20 @@ static void setup_sni(MoeMojiApplication *self) {
|
|||||||
g_dbus_node_info_unref(menu_node);
|
g_dbus_node_info_unref(menu_node);
|
||||||
if (self->menu_registration_id == 0) {
|
if (self->menu_registration_id == 0) {
|
||||||
g_warning("dbusmenu register: %s", error->message);
|
g_warning("dbusmenu register: %s", error->message);
|
||||||
g_error_free(error);
|
g_clear_error(&error);
|
||||||
}
|
}
|
||||||
g_autofree gchar *bus_name =
|
const gchar *unique_name = g_dbus_connection_get_unique_name(self->dbus_conn);
|
||||||
g_strdup_printf("org.kde.StatusNotifierItem-%d-1", getpid());
|
g_dbus_connection_call(
|
||||||
self->sni_bus_name_id = g_bus_own_name_on_connection(
|
self->dbus_conn, "org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher",
|
||||||
self->dbus_conn, bus_name, G_BUS_NAME_OWNER_FLAGS_NONE,
|
"org.kde.StatusNotifierWatcher", "RegisterStatusNotifierItem",
|
||||||
on_sni_bus_name_acquired, NULL, NULL, NULL);
|
g_variant_new("(s)", unique_name), NULL, G_DBUS_CALL_FLAGS_NONE, -1,
|
||||||
|
NULL, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void on_shortcuts_activated(GDBusProxy *proxy, const gchar *sender_name,
|
static void on_shortcuts_activated(G_GNUC_UNUSED GDBusProxy *proxy,
|
||||||
|
G_GNUC_UNUSED const gchar *sender_name,
|
||||||
const gchar *signal_name,
|
const gchar *signal_name,
|
||||||
GVariant *parameters, gpointer user_data) {
|
GVariant *parameters, gpointer user_data) {
|
||||||
(void)proxy;
|
|
||||||
(void)sender_name;
|
|
||||||
if (g_strcmp0(signal_name, "Activated") != 0)
|
if (g_strcmp0(signal_name, "Activated") != 0)
|
||||||
return;
|
return;
|
||||||
MoeMojiApplication *self = MOEMOJI_APPLICATION(user_data);
|
MoeMojiApplication *self = MOEMOJI_APPLICATION(user_data);
|
||||||
@@ -490,7 +508,7 @@ static void setup_global_shortcuts(MoeMojiApplication *self) {
|
|||||||
"org.freedesktop.portal.GlobalShortcuts", NULL, &error);
|
"org.freedesktop.portal.GlobalShortcuts", NULL, &error);
|
||||||
if (self->shortcuts_proxy == NULL) {
|
if (self->shortcuts_proxy == NULL) {
|
||||||
g_warning("GlobalShortcuts: Failed to create proxy: %s", error->message);
|
g_warning("GlobalShortcuts: Failed to create proxy: %s", error->message);
|
||||||
g_error_free(error);
|
g_clear_error(&error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
g_timeout_add(500, (GSourceFunc)begin_create_session, self);
|
g_timeout_add(500, (GSourceFunc)begin_create_session, self);
|
||||||
@@ -507,8 +525,8 @@ static void moemoji_application_startup(GApplication *app) {
|
|||||||
MoeMojiApplication *self = MOEMOJI_APPLICATION(app);
|
MoeMojiApplication *self = MOEMOJI_APPLICATION(app);
|
||||||
gtk_icon_theme_add_resource_path(
|
gtk_icon_theme_add_resource_path(
|
||||||
gtk_icon_theme_get_for_display(gdk_display_get_default()),
|
gtk_icon_theme_get_for_display(gdk_display_get_default()),
|
||||||
"/net/angeltech/MoeMoji/icons");
|
"/jp/angeltech/MoeMoji/icons");
|
||||||
gtk_window_set_default_icon_name("net.angeltech.MoeMoji-symbolic");
|
gtk_window_set_default_icon_name("jp.angeltech.MoeMoji-symbolic");
|
||||||
gboolean in_flatpak = g_file_test("/.flatpak-info", G_FILE_TEST_EXISTS);
|
gboolean in_flatpak = g_file_test("/.flatpak-info", G_FILE_TEST_EXISTS);
|
||||||
if (in_flatpak) {
|
if (in_flatpak) {
|
||||||
self->icon_theme_path = NULL;
|
self->icon_theme_path = NULL;
|
||||||
@@ -542,7 +560,7 @@ static void moemoji_application_activate(GApplication *app) {
|
|||||||
self->window = g_object_new(MOEMOJI_TYPE_WINDOW, "application",
|
self->window = g_object_new(MOEMOJI_TYPE_WINDOW, "application",
|
||||||
GTK_APPLICATION(self), NULL);
|
GTK_APPLICATION(self), NULL);
|
||||||
g_signal_connect(self->window, "close-request",
|
g_signal_connect(self->window, "close-request",
|
||||||
G_CALLBACK(on_close_request), NULL);
|
G_CALLBACK(on_close_request), self);
|
||||||
self->window_created = TRUE;
|
self->window_created = TRUE;
|
||||||
} else {
|
} else {
|
||||||
toggle_window(self);
|
toggle_window(self);
|
||||||
@@ -561,10 +579,6 @@ static void moemoji_application_dispose(GObject *object) {
|
|||||||
self->menu_registration_id);
|
self->menu_registration_id);
|
||||||
self->menu_registration_id = 0;
|
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_object(&self->shortcuts_proxy);
|
||||||
g_clear_pointer(&self->shortcuts_session_path, g_free);
|
g_clear_pointer(&self->shortcuts_session_path, g_free);
|
||||||
g_clear_pointer(&self->icon_theme_path, g_free);
|
g_clear_pointer(&self->icon_theme_path, g_free);
|
||||||
@@ -592,7 +606,7 @@ static void moemoji_application_class_init(MoeMojiApplicationClass *klass) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void moemoji_application_init(MoeMojiApplication *self) {
|
static void moemoji_application_init(MoeMojiApplication *self) {
|
||||||
self->settings = g_settings_new("net.angeltech.MoeMoji");
|
self->settings = g_settings_new("jp.angeltech.MoeMoji");
|
||||||
self->window_created = FALSE;
|
self->window_created = FALSE;
|
||||||
self->shortcuts_bound = FALSE;
|
self->shortcuts_bound = FALSE;
|
||||||
g_autoptr(GSimpleAction) quit_action = g_simple_action_new("quit", NULL);
|
g_autoptr(GSimpleAction) quit_action = g_simple_action_new("quit", NULL);
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ struct _MoeMojiApplication {
|
|||||||
gboolean shortcuts_bound;
|
gboolean shortcuts_bound;
|
||||||
GDBusConnection *dbus_conn;
|
GDBusConnection *dbus_conn;
|
||||||
guint sni_registration_id;
|
guint sni_registration_id;
|
||||||
guint sni_bus_name_id;
|
|
||||||
guint menu_registration_id;
|
guint menu_registration_id;
|
||||||
gchar *icon_theme_path;
|
gchar *icon_theme_path;
|
||||||
const gchar *tray_icon_name;
|
const gchar *tray_icon_name;
|
||||||
GDBusProxy *shortcuts_proxy;
|
GDBusProxy *shortcuts_proxy;
|
||||||
gchar *shortcuts_session_path;
|
gchar *shortcuts_session_path;
|
||||||
|
gboolean has_tray;
|
||||||
};
|
};
|
||||||
|
|
||||||
G_BEGIN_DECLS
|
G_BEGIN_DECLS
|
||||||
|
|||||||
@@ -62,6 +62,13 @@ static void on_popover_leave(G_GNUC_UNUSED GtkEventControllerMotion *ctrl,
|
|||||||
gtk_popover_popdown(GTK_POPOVER(user_data));
|
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) {
|
static void add_kaomoji_button(GtkFlowBox *flow, const char *text) {
|
||||||
gboolean multiline = (strchr(text, '\n') != NULL);
|
gboolean multiline = (strchr(text, '\n') != NULL);
|
||||||
const char *label_text = text;
|
const char *label_text = text;
|
||||||
@@ -88,6 +95,8 @@ static void add_kaomoji_button(GtkFlowBox *flow, const char *text) {
|
|||||||
gtk_widget_add_css_class(label, "kaomoji-preview");
|
gtk_widget_add_css_class(label, "kaomoji-preview");
|
||||||
gtk_popover_set_child(GTK_POPOVER(popover), label);
|
gtk_popover_set_child(GTK_POPOVER(popover), label);
|
||||||
gtk_widget_set_parent(popover, button);
|
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();
|
GtkEventController *motion = gtk_event_controller_motion_new();
|
||||||
g_signal_connect(motion, "enter", G_CALLBACK(on_popover_enter), popover);
|
g_signal_connect(motion, "enter", G_CALLBACK(on_popover_enter), popover);
|
||||||
@@ -139,47 +148,127 @@ static void load_category(MoeMojiWindow *self, const char *kaomoji_dir,
|
|||||||
g_free(cat_path);
|
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) {
|
static void on_chip_clicked(GtkButton *button, gpointer user_data) {
|
||||||
MoeMojiWindow *self = MOEMOJI_WINDOW(user_data);
|
MoeMojiWindow *self = MOEMOJI_WINDOW(user_data);
|
||||||
const char *current = gtk_editable_get_text(GTK_EDITABLE(self->search_entry));
|
for (guint i = 0; i < self->category_widgets->len; i++) {
|
||||||
const char *name = gtk_button_get_label(button);
|
CategoryWidgets *cw = g_ptr_array_index(self->category_widgets, i);
|
||||||
if (g_strcmp0(current, name) == 0)
|
if (cw->chip == GTK_WIDGET(button)) {
|
||||||
gtk_editable_set_text(GTK_EDITABLE(self->search_entry), "");
|
scroll_to_category(self, (int)i);
|
||||||
else
|
return;
|
||||||
gtk_editable_set_text(GTK_EDITABLE(self->search_entry), name);
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void on_search_changed(GtkSearchEntry *entry, gpointer user_data) {
|
static void on_search_changed(GtkSearchEntry *entry, gpointer user_data) {
|
||||||
MoeMojiWindow *self = MOEMOJI_WINDOW(user_data);
|
MoeMojiWindow *self = MOEMOJI_WINDOW(user_data);
|
||||||
const char *query = gtk_editable_get_text(GTK_EDITABLE(entry));
|
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++) {
|
for (guint i = 0; i < self->category_widgets->len; i++) {
|
||||||
CategoryWidgets *cw = g_ptr_array_index(self->category_widgets, i);
|
CategoryWidgets *cw = g_ptr_array_index(self->category_widgets, i);
|
||||||
gboolean visible;
|
g_autofree char *name_lower = g_utf8_strdown(cw->name, -1);
|
||||||
|
if (strstr(name_lower, query_lower) != NULL) {
|
||||||
if (query == NULL || query[0] == '\0') {
|
scroll_to_category(self, (int)i);
|
||||||
visible = TRUE;
|
return;
|
||||||
} 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) {
|
static void moemoji_window_class_init(MoeMojiWindowClass *klass) {
|
||||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
|
||||||
gtk_widget_class_set_template_from_resource(
|
gtk_widget_class_set_template_from_resource(
|
||||||
widget_class, "/net/angeltech/MoeMoji/moemoji-window.ui");
|
widget_class, "/jp/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, outer_box);
|
||||||
gtk_widget_class_bind_template_child(widget_class, MoeMojiWindow,
|
gtk_widget_class_bind_template_child(widget_class, MoeMojiWindow,
|
||||||
content_box);
|
content_box);
|
||||||
gtk_widget_class_bind_template_child(widget_class, MoeMojiWindow,
|
gtk_widget_class_bind_template_child(widget_class, MoeMojiWindow,
|
||||||
search_entry);
|
search_entry);
|
||||||
gtk_widget_class_bind_template_child(widget_class, MoeMojiWindow, header_bar);
|
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,
|
static void collect_categories(const char *base_dir, GHashTable *seen,
|
||||||
@@ -218,6 +307,7 @@ static void moemoji_window_init(MoeMojiWindow *self) {
|
|||||||
gtk_widget_add_css_class(GTK_WIDGET(self->content_box), "content-area");
|
gtk_widget_add_css_class(GTK_WIDGET(self->content_box), "content-area");
|
||||||
self->category_widgets =
|
self->category_widgets =
|
||||||
g_ptr_array_new_with_free_func(category_widgets_free);
|
g_ptr_array_new_with_free_func(category_widgets_free);
|
||||||
|
self->active_chip_index = -1;
|
||||||
char *kaomoji_dir = find_kaomoji_dir();
|
char *kaomoji_dir = find_kaomoji_dir();
|
||||||
g_autofree char *user_dir =
|
g_autofree char *user_dir =
|
||||||
g_build_filename(g_get_user_data_dir(), "moemoji", "kaomoji", NULL);
|
g_build_filename(g_get_user_data_dir(), "moemoji", "kaomoji", NULL);
|
||||||
@@ -246,6 +336,8 @@ static void moemoji_window_init(MoeMojiWindow *self) {
|
|||||||
}
|
}
|
||||||
g_ptr_array_free(entries, TRUE);
|
g_ptr_array_free(entries, TRUE);
|
||||||
g_free(kaomoji_dir);
|
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_signal_connect(self->search_entry, "search-changed",
|
||||||
G_CALLBACK(on_search_changed), self);
|
G_CALLBACK(on_search_changed), self);
|
||||||
if (self->category_widgets->len > 0) {
|
if (self->category_widgets->len > 0) {
|
||||||
@@ -260,6 +352,7 @@ static void moemoji_window_init(MoeMojiWindow *self) {
|
|||||||
gtk_widget_add_css_class(btn, "category-chip");
|
gtk_widget_add_css_class(btn, "category-chip");
|
||||||
gtk_widget_add_css_class(btn, "flat");
|
gtk_widget_add_css_class(btn, "flat");
|
||||||
g_signal_connect(btn, "clicked", G_CALLBACK(on_chip_clicked), self);
|
g_signal_connect(btn, "clicked", G_CALLBACK(on_chip_clicked), self);
|
||||||
|
cw->chip = btn;
|
||||||
gtk_flow_box_insert(GTK_FLOW_BOX(flow), btn, -1);
|
gtk_flow_box_insert(GTK_FLOW_BOX(flow), btn, -1);
|
||||||
}
|
}
|
||||||
self->category_bar = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
|
self->category_bar = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0));
|
||||||
@@ -275,5 +368,14 @@ static void moemoji_window_init(MoeMojiWindow *self) {
|
|||||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(cat_scroll), flow);
|
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(cat_scroll), flow);
|
||||||
gtk_box_append(self->category_bar, cat_scroll);
|
gtk_box_append(self->category_bar, cat_scroll);
|
||||||
gtk_box_append(self->outer_box, GTK_WIDGET(self->category_bar));
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
GtkWidget *header;
|
GtkWidget *header;
|
||||||
GtkWidget *flow;
|
GtkWidget *flow;
|
||||||
|
GtkWidget *chip;
|
||||||
char *name;
|
char *name;
|
||||||
} CategoryWidgets;
|
} CategoryWidgets;
|
||||||
|
|
||||||
@@ -15,7 +16,10 @@ struct _MoeMojiWindow {
|
|||||||
GtkSearchEntry *search_entry;
|
GtkSearchEntry *search_entry;
|
||||||
GtkWidget *header_bar;
|
GtkWidget *header_bar;
|
||||||
GtkBox *category_bar;
|
GtkBox *category_bar;
|
||||||
|
GtkWidget *kaomoji_scroll;
|
||||||
GPtrArray *category_widgets;
|
GPtrArray *category_widgets;
|
||||||
|
GtkWidget *bottom_spacer;
|
||||||
|
int active_chip_index;
|
||||||
};
|
};
|
||||||
|
|
||||||
G_BEGIN_DECLS
|
G_BEGIN_DECLS
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
<interface>
|
<interface>
|
||||||
<requires lib="gtk" version="4.0" />
|
<requires lib="gtk" version="4.0" />
|
||||||
<template class="MoeMojiWindow" parent="GtkApplicationWindow">
|
<template class="MoeMojiWindow" parent="GtkApplicationWindow">
|
||||||
<property name="default-width">280</property>
|
<property name="default-width">680</property>
|
||||||
<property name="default-height">600</property>
|
<property name="default-height">400</property>
|
||||||
<property name="resizable">True</property>
|
<property name="resizable">True</property>
|
||||||
<property name="title">MoeMoji</property>
|
<property name="title">MoeMoji</property>
|
||||||
|
|
||||||
@@ -18,14 +18,14 @@
|
|||||||
<property name="orientation">vertical</property>
|
<property name="orientation">vertical</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkSearchEntry" id="search_entry">
|
<object class="GtkSearchEntry" id="search_entry">
|
||||||
<property name="placeholder-text">Filter by category...</property>
|
<property name="placeholder-text">Jump to category...</property>
|
||||||
<property name="margin-start">6</property>
|
<property name="margin-start">6</property>
|
||||||
<property name="margin-end">6</property>
|
<property name="margin-end">6</property>
|
||||||
<property name="margin-top">6</property>
|
<property name="margin-top">6</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkScrolledWindow">
|
<object class="GtkScrolledWindow" id="kaomoji_scroll">
|
||||||
<property name="hscrollbar-policy">never</property>
|
<property name="hscrollbar-policy">never</property>
|
||||||
<property name="vexpand">True</property>
|
<property name="vexpand">True</property>
|
||||||
<child>
|
<child>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<gresources>
|
<gresources>
|
||||||
<gresource prefix="/net/angeltech/MoeMoji">
|
<gresource prefix="/jp/angeltech/MoeMoji">
|
||||||
<file>moemoji-window.ui</file>
|
<file>moemoji-window.ui</file>
|
||||||
<file>style.css</file>
|
<file>style.css</file>
|
||||||
<file>wp.png</file>
|
<file>wp.png</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/jp.angeltech.MoeMoji.svg">../data/icons/hicolor/scalable/apps/jp.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>
|
<file alias="icons/scalable/apps/jp.angeltech.MoeMoji-symbolic.svg">../data/icons/hicolor/symbolic/apps/jp.angeltech.MoeMoji-symbolic.svg</file>
|
||||||
</gresource>
|
</gresource>
|
||||||
</gresources>
|
</gresources>
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wallpaper-bg {
|
.wallpaper-bg {
|
||||||
background-image: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url("/net/angeltech/MoeMoji/wp.png");
|
background-image: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url("/jp/angeltech/MoeMoji/wp.png");
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
@@ -53,3 +53,8 @@ wallpaper-bg > * {
|
|||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chip-active {
|
||||||
|
background-color: rgba(255, 255, 255, 0.25);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <glib/gstdio.h>
|
#include <glib/gstdio.h>
|
||||||
|
#include <gtk/gtk.h>
|
||||||
#include "moemoji-internal.h"
|
#include "moemoji-internal.h"
|
||||||
|
#include "moemoji-window.h"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
test_display_name_underscores (void)
|
test_display_name_underscores (void)
|
||||||
@@ -45,16 +47,16 @@ test_display_name_empty (void)
|
|||||||
static void
|
static void
|
||||||
test_find_kaomoji_with_env (void)
|
test_find_kaomoji_with_env (void)
|
||||||
{
|
{
|
||||||
g_setenv ("MESON_SOURCE_ROOT", SRCDIR, TRUE);
|
|
||||||
char *dir = find_kaomoji_dir ();
|
char *dir = find_kaomoji_dir ();
|
||||||
g_assert_nonnull (dir);
|
g_assert_nonnull (dir);
|
||||||
g_assert_true (g_file_test (dir, G_FILE_TEST_IS_DIR));
|
g_assert_true (g_file_test (dir, G_FILE_TEST_IS_DIR));
|
||||||
g_free (dir);
|
g_free (dir);
|
||||||
g_unsetenv ("MESON_SOURCE_ROOT");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gboolean find_kaomoji_bogus_passed = FALSE;
|
||||||
|
|
||||||
static void
|
static void
|
||||||
test_find_kaomoji_bogus (void)
|
run_find_kaomoji_bogus (void)
|
||||||
{
|
{
|
||||||
g_setenv ("MESON_SOURCE_ROOT", "/nonexistent", TRUE);
|
g_setenv ("MESON_SOURCE_ROOT", "/nonexistent", TRUE);
|
||||||
char *saved = g_get_current_dir ();
|
char *saved = g_get_current_dir ();
|
||||||
@@ -65,7 +67,14 @@ test_find_kaomoji_bogus (void)
|
|||||||
|
|
||||||
g_assert_true (g_chdir (saved) == 0);
|
g_assert_true (g_chdir (saved) == 0);
|
||||||
g_free (saved);
|
g_free (saved);
|
||||||
g_unsetenv ("MESON_SOURCE_ROOT");
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -145,27 +154,141 @@ test_dbusmenu_unknown (void)
|
|||||||
g_assert_null (v);
|
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
|
int
|
||||||
main (int argc, char *argv[])
|
main (int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
g_setenv ("MESON_SOURCE_ROOT", SRCDIR, TRUE);
|
||||||
g_test_init (&argc, &argv, NULL);
|
g_test_init (&argc, &argv, NULL);
|
||||||
|
|
||||||
g_test_add_func ("/display-name/underscores", test_display_name_underscores);
|
run_find_kaomoji_bogus ();
|
||||||
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);
|
gboolean have_display = gtk_init_check ();
|
||||||
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 ("/display-name/underscores", test_display_name_underscores);
|
||||||
g_test_add_func ("/find-kaomoji/with-env", test_find_kaomoji_with_env);
|
g_test_add_func ("/display-name/no-underscores", test_display_name_no_underscores);
|
||||||
g_test_add_func ("/find-kaomoji/bogus", test_find_kaomoji_bogus);
|
g_test_add_func ("/display-name/already-upper", test_display_name_already_upper);
|
||||||
g_test_add_func ("/sni/category", test_sni_category);
|
g_test_add_func ("/display-name/single-char", test_display_name_single_char);
|
||||||
g_test_add_func ("/sni/id", test_sni_id);
|
g_test_add_func ("/display-name/empty", test_display_name_empty);
|
||||||
g_test_add_func ("/sni/item-is-menu", test_sni_item_is_menu);
|
g_test_add_func ("/find-kaomoji/with-env", test_find_kaomoji_with_env);
|
||||||
g_test_add_func ("/sni/menu", test_sni_menu);
|
g_test_add_func ("/find-kaomoji/bogus", test_find_kaomoji_bogus);
|
||||||
g_test_add_func ("/sni/unknown", test_sni_unknown);
|
g_test_add_func ("/sni/category", test_sni_category);
|
||||||
g_test_add_func ("/dbusmenu/version", test_dbusmenu_version);
|
g_test_add_func ("/sni/id", test_sni_id);
|
||||||
g_test_add_func ("/dbusmenu/status", test_dbusmenu_status);
|
g_test_add_func ("/sni/item-is-menu", test_sni_item_is_menu);
|
||||||
g_test_add_func ("/dbusmenu/text-direction", test_dbusmenu_text_direction);
|
g_test_add_func ("/sni/menu", test_sni_menu);
|
||||||
g_test_add_func ("/dbusmenu/unknown", test_dbusmenu_unknown);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
return g_test_run ();
|
return g_test_run ();
|
||||||
}
|
}
|
||||||
|
|||||||