#include #include #include #include "zero-pinyin-service.h" #include "zero-pinyin-service-generated.h" #include "../sqlite3_util.h" #include typedef struct { GApplication *app; guint owner_id; ZeroPinyinService *interface; sqlite3 *db; } AppData; static gboolean on_handle_get_candidates (ZeroPinyinService *object, GDBusMethodInvocation *invocation, const gchar *preedit_str, guint fetch_size, AppData *appdata) { if (preedit_str == NULL || fetch_size == 0) { g_dbus_method_invocation_return_dbus_error ( invocation, "org.gtk.GDBus.Failed", "Bad param"); return TRUE; } g_message ("get_candidates for preedit_str=%s fetch_size=%u", preedit_str, fetch_size); GVariant *result = NULL; GVariantBuilder *candidates_builder = NULL; GVariantBuilder *matched_lengths_builder = NULL; GVariantBuilder *candidates_pinyin_indices = NULL; /* test data */ /* get_candidates_test (preedit_str, fetch_size, candidates_builder, matched_lengths_builder); */ candidates_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); matched_lengths_builder = g_variant_builder_new (G_VARIANT_TYPE ("au")); candidates_pinyin_indices = g_variant_builder_new (G_VARIANT_TYPE ("aa(ii)")); get_candidates (appdata->db, preedit_str, fetch_size, candidates_builder, matched_lengths_builder, candidates_pinyin_indices); result = g_variant_new ("(asauaa(ii))", candidates_builder, matched_lengths_builder, candidates_pinyin_indices); g_assert_nonnull (result); /* result is a GVarient tuple of two dbus arrays */ g_dbus_method_invocation_return_value (invocation, result); g_variant_builder_unref (candidates_builder); g_variant_builder_unref (matched_lengths_builder); g_variant_builder_unref (candidates_pinyin_indices); return TRUE; } static gboolean on_handle_commit_candidate (ZeroPinyinService *object, GDBusMethodInvocation *invocation, const gchar *candidate, GVariant *candidate_pinyin_indices, AppData *appdata) { commit_candidate (appdata->db, candidate, candidate_pinyin_indices); g_dbus_method_invocation_return_value (invocation, NULL); return TRUE; } static gboolean on_handle_delete_candidate (ZeroPinyinService *object, GDBusMethodInvocation *invocation, const char *candidate, AppData *appdata) { if (! candidate) { g_dbus_method_invocation_return_value (invocation, NULL); return TRUE; } guint len = g_utf8_strlen (candidate, -1); if (len == 1) { g_message ("delete single character %s is a no-op", candidate); g_dbus_method_invocation_return_value (invocation, NULL); return TRUE; } g_message ("delete candidate %s", candidate); /* insert phrase to userdb.not_phrase table. */ char *sql = NULL; gboolean rb = FALSE; sql = sqlite3_mprintf ("INSERT INTO userdb.not_phrase (phrase) VALUES (%Q);", candidate); rb = sqlite3_exec_simple (appdata->db, sql); if (! rb) { g_warning ("insert phrase to not_phrase table failed"); } sqlite3_free (sql); /* delete phrase from userdb.py_phrase_x table. */ guint table_suffix = len - 1; sql = sqlite3_mprintf ("DELETE FROM userdb.py_phrase_%u WHERE phrase = %Q;", table_suffix, candidate); rb = sqlite3_exec_simple (appdata->db, sql); if (! rb) { g_warning ("delete phrase from py_phrase_%u table failed", table_suffix); } sqlite3_free (sql); g_dbus_method_invocation_return_value (invocation, NULL); return TRUE; } static gboolean on_handle_quit (ZeroPinyinService *object, GDBusMethodInvocation *invocation, AppData *appdata) { g_application_quit (appdata->app); g_dbus_method_invocation_return_value (invocation, NULL); return TRUE; } static void on_bus_acquired (GDBusConnection *connection, const gchar *name, gpointer user_data) { AppData *appdata = (AppData*) user_data; GError *err = NULL; g_message ("on_bus_acquired() name=%s", name); appdata->interface = zero_pinyin_service_skeleton_new (); g_signal_connect (appdata->interface, "handle-get-candidates", G_CALLBACK (on_handle_get_candidates), appdata); g_signal_connect (appdata->interface, "handle-commit-candidate", G_CALLBACK (on_handle_commit_candidate), appdata); g_signal_connect (appdata->interface, "handle-delete-candidate", G_CALLBACK (on_handle_delete_candidate), appdata); g_signal_connect (appdata->interface, "handle-quit", G_CALLBACK (on_handle_quit), appdata); g_dbus_interface_skeleton_export ( G_DBUS_INTERFACE_SKELETON (appdata->interface), connection, ZERO_PINYIN_OBJECT_PATH, &err); if (err) { g_warning ("export interface at %s failed: %s", ZERO_PINYIN_OBJECT_PATH, err->message); g_error_free (err); g_application_quit (G_APPLICATION (appdata->app)); return; } g_message ("interface exported at %s", ZERO_PINYIN_OBJECT_PATH); } static void on_name_acquired (GDBusConnection *connection, const gchar *name, gpointer user_data) { g_message ("on_name_acquired() name=%s", name); } static void on_name_lost (GDBusConnection *connection, const gchar *name, gpointer user_data) { AppData *appdata = (AppData*) user_data; /* this won't happen if this is the only app that tries to take the * name, because GApplication already have primary instance * concept. None primary instance will just send 'activate' signal to * primary instance and exit. They will not try to register ibus at * all. */ g_message ("on_name_lost() name=%s exiting", name); g_application_quit (G_APPLICATION (appdata->app)); } static void config_dbus_service (AppData *appdata) { appdata->owner_id = g_bus_own_name ( G_BUS_TYPE_SESSION, ZERO_PINYIN_WELL_KNOWN_NAME, G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT|G_BUS_NAME_OWNER_FLAGS_REPLACE, on_bus_acquired, on_name_acquired, on_name_lost, appdata, NULL); g_assert_cmpint (appdata->owner_id, >, 0); } /** * handle SIGTERM gracefully. */ static gboolean on_sigterm_received (gpointer user_data) { AppData *appdata = (AppData*) user_data; g_application_quit (appdata->app); return G_SOURCE_REMOVE; } /** * init appdata->db */ static void config_db (AppData* appdata) { gint ri = 0; gboolean rb = FALSE; static const char* SQLITE3_MEMORY_DB = ":memory:"; sqlite3* db = NULL; gchar* sql = NULL; ri = sqlite3_open (SQLITE3_MEMORY_DB, &db); if (ri != SQLITE_OK) { g_warning ("sqlite3_open :memory: db failed, query will not work."); goto db_fail; } g_assert_nonnull (db); /* TODO make db path configurable */ /* TODO remove user name in db path */ sql = sqlite3_mprintf ("ATTACH %Q AS maindb", "/home/sylecn/.cache/ibus/pinyin/main.db"); rb = sqlite3_exec_simple (db, sql); if (! rb) { g_warning ("attach maindb failed, query will not work."); goto attach_fail; } sqlite3_free (sql); /* TODO make db path configurable */ sql = sqlite3_mprintf ("ATTACH %Q AS userdb", "/home/sylecn/.cache/ibus/pinyin/user-1.0.db"); rb = sqlite3_exec_simple (db, sql); if (! rb) { g_warning ("attach userdb failed, query will not work."); goto attach_fail; } sqlite3_free (sql); sql = "CREATE TABLE IF NOT EXISTS userdb.not_phrase (phrase TEXT UNIQUE);"; rb = sqlite3_exec_simple (db, sql); if (! rb) { g_warning ("create userdb.not_phrase table failed, query will not work."); sql = NULL; goto attach_fail; } appdata->db = db; return; attach_fail: sqlite3_free (sql); sqlite3_close (db); db_fail: appdata->db = NULL; } /** * allow graceful shutdown by Ctrl-C and SIGTERM. */ static void setup_sigint_sigterm_handler (AppData *appdata) { GSource *source = NULL; source = g_unix_signal_source_new (SIGTERM); g_source_set_callback (source, on_sigterm_received, appdata, NULL); g_source_attach (source, NULL); g_source_unref (source); source = g_unix_signal_source_new (SIGINT); g_source_set_callback (source, on_sigterm_received, appdata, NULL); g_source_attach (source, NULL); g_source_unref (source); } static void on_startup (GApplication* app, AppData* appdata) { g_message ("zero-pinyin-service startup()"); config_db (appdata); config_dbus_service (appdata); setup_sigint_sigterm_handler (appdata); g_application_hold (app); } static void on_activate (GApplication *app, AppData *appdata) { g_message ("zero-pinyin-service activate()"); } static void on_shutdown (GApplication *app, AppData *appdata) { g_message ("zero-pinyin-service shutdown()"); if (appdata->owner_id > 0) { g_bus_unown_name (appdata->owner_id); appdata->owner_id = 0; } if (appdata->db != NULL) { sqlite3_close (appdata->db); appdata->db = NULL; } } /** * provides zero-pinyin-service dbus service. * it's a console app (GApplication) based on glib and gio. */ int main (int argc, char *argv[]) { static AppData appdata = {0}; GApplication *app = NULL; int status = 0; setlocale (LC_ALL, ""); app = g_application_new ("com.emacsos.zero.ZeroPinyinServiceApp", G_APPLICATION_FLAGS_NONE); g_assert_nonnull (app); appdata.app = app; g_signal_connect (app, "startup", G_CALLBACK (on_startup), &appdata); g_signal_connect (app, "activate", G_CALLBACK (on_activate), &appdata); g_signal_connect (app, "shutdown", G_CALLBACK (on_shutdown), &appdata); status = g_application_run (G_APPLICATION (app), argc, argv); g_object_unref (app); return status; }