You've already forked android_translation_layer
mirror of
https://gitlab.com/android_translation_layer/android_translation_layer.git
synced 2025-10-27 11:48:10 -07:00
ATLKeyboard: Integrate with phosh and wayland IME
This commit is contained in:
301
src/api-impl-jni/android_inputmethodservice_InputMethodService.c
Normal file
301
src/api-impl-jni/android_inputmethodservice_InputMethodService.c
Normal file
@@ -0,0 +1,301 @@
|
||||
#include <jni.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <gdk/wayland/gdkwayland.h>
|
||||
|
||||
#include "input-method-unstable-v2-client-protocol.h"
|
||||
#include "virtual-keyboard-unstable-v1-client-protocol.h"
|
||||
|
||||
#include "defines.h"
|
||||
#include "util.h"
|
||||
#include "generated_headers/android_inputmethodservice_InputMethodService_ATLInputConnection.h"
|
||||
|
||||
#define INFO(x...) android_log_printf(ANDROID_LOG_INFO, "ATLKeyboardIMS", x)
|
||||
#define DEBUG(fmt, ...) android_log_printf(ANDROID_LOG_DEBUG, "ATLKeyboardIMS", "%s:%d: " fmt, __func__, __LINE__, ##__VA_ARGS__)
|
||||
|
||||
struct {
|
||||
struct wl_display *display;
|
||||
struct wl_registry *registry;
|
||||
struct wl_seat *seat;
|
||||
struct zwp_input_method_manager_v2 *input_method_manager;
|
||||
struct zwp_input_method_v2 *input_method;
|
||||
struct zwp_virtual_keyboard_manager_v1 *virtual_keyboard_manager;
|
||||
struct zwp_virtual_keyboard_v1 *virtual_keyboard;
|
||||
|
||||
char compositing[4096];
|
||||
char surrounding[4096];
|
||||
guint text_len;
|
||||
guint cursor;
|
||||
guint serial;
|
||||
} osk = {0};
|
||||
|
||||
/* android_app_ATLKeyboardDialog.c */
|
||||
#ifdef ATL_HAS_OSK
|
||||
extern void atlosk_set_visible(gboolean new_visible);
|
||||
#else
|
||||
static void atlosk_set_visible(gboolean new_visible)
|
||||
{
|
||||
DEBUG("=%d\n", new_visible);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* input-method-unstable-v2
|
||||
*/
|
||||
|
||||
static void
|
||||
handle_activate(void *data,
|
||||
struct zwp_input_method_v2 *zwp_input_method_v2)
|
||||
{
|
||||
atlosk_set_visible(TRUE);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_deactivate(void *data,
|
||||
struct zwp_input_method_v2 *zwp_input_method_v2)
|
||||
{
|
||||
atlosk_set_visible(FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_surrounding_text (void *data,
|
||||
struct zwp_input_method_v2 *zwp_input_method_v2,
|
||||
const char *text,
|
||||
uint32_t cursor,
|
||||
uint32_t anchor)
|
||||
{
|
||||
DEBUG("(cursor=%d, '%s')\n", cursor, text);
|
||||
osk.cursor = cursor;
|
||||
osk.text_len = strnlen(text, sizeof(osk.surrounding));
|
||||
strncpy(osk.surrounding, text, sizeof(osk.surrounding));
|
||||
}
|
||||
|
||||
static void
|
||||
handle_text_change_cause (void *data,
|
||||
struct zwp_input_method_v2 *zwp_input_method_v2,
|
||||
uint32_t cause)
|
||||
{
|
||||
//DEBUG("\n");
|
||||
}
|
||||
|
||||
static void
|
||||
handle_content_type (void *data,
|
||||
struct zwp_input_method_v2 *zwp_input_method_v2,
|
||||
uint32_t hint,
|
||||
uint32_t purpose)
|
||||
{
|
||||
//DEBUG("\n");
|
||||
}
|
||||
|
||||
static void
|
||||
handle_done (void *data,
|
||||
struct zwp_input_method_v2 *zwp_input_method_v2)
|
||||
{
|
||||
osk.serial++;
|
||||
}
|
||||
|
||||
static void
|
||||
handle_unavailable (void *data,
|
||||
struct zwp_input_method_v2 *zwp_input_method_v2)
|
||||
{
|
||||
INFO("Input method unavailable");
|
||||
}
|
||||
|
||||
static const struct zwp_input_method_v2_listener input_method_listener = {
|
||||
.activate = handle_activate,
|
||||
.deactivate = handle_deactivate,
|
||||
.surrounding_text = handle_surrounding_text,
|
||||
.text_change_cause = handle_text_change_cause,
|
||||
.content_type = handle_content_type,
|
||||
.done = handle_done,
|
||||
.unavailable = handle_unavailable,
|
||||
};
|
||||
|
||||
static void
|
||||
registry_handle_global (void *data,
|
||||
struct wl_registry *registry,
|
||||
uint32_t name,
|
||||
const char *interface,
|
||||
uint32_t version)
|
||||
{
|
||||
if (!strcmp (interface, zwp_input_method_manager_v2_interface.name)) {
|
||||
osk.input_method_manager = wl_registry_bind(registry, name, &zwp_input_method_manager_v2_interface, 1);
|
||||
osk.input_method = zwp_input_method_manager_v2_get_input_method(osk.input_method_manager, osk.seat);
|
||||
zwp_input_method_v2_add_listener (osk.input_method, &input_method_listener, &osk);
|
||||
} else if (!strcmp (interface, zwp_virtual_keyboard_manager_v1_interface.name)) {
|
||||
osk.virtual_keyboard_manager = wl_registry_bind(registry, name, &zwp_virtual_keyboard_manager_v1_interface, 1);
|
||||
osk.virtual_keyboard = zwp_virtual_keyboard_manager_v1_create_virtual_keyboard(osk.virtual_keyboard_manager, osk.seat);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
registry_handle_global_remove (void *data,
|
||||
struct wl_registry *registry,
|
||||
uint32_t name)
|
||||
{
|
||||
INFO("Global %d removed but not handled", name);
|
||||
}
|
||||
|
||||
static const struct wl_registry_listener registry_listener = {
|
||||
registry_handle_global,
|
||||
registry_handle_global_remove,
|
||||
};
|
||||
|
||||
JNIEXPORT jlong JNICALL Java_android_inputmethodservice_InputMethodService_00024ATLInputConnection_nativeInit
|
||||
(JNIEnv *env, jobject this)
|
||||
{
|
||||
GdkDisplay *gdk_display;
|
||||
GdkSeat *gdk_seat;
|
||||
extern GtkWindow *window; /* Main activity window. */
|
||||
|
||||
INFO("Native init!");
|
||||
|
||||
gdk_display = gtk_root_get_display(GTK_ROOT(window));
|
||||
if (gdk_display == NULL) {
|
||||
g_critical ("ATLKeyboardIMS: Failed to get display: %m\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
gdk_seat = gdk_display_get_default_seat(gdk_display);
|
||||
if (gdk_seat == NULL) {
|
||||
g_critical ("ATLKeyboardIMS: Failed to get seat: %m\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
osk.display = gdk_wayland_display_get_wl_display (gdk_display);
|
||||
osk.seat = gdk_wayland_seat_get_wl_seat(gdk_seat);
|
||||
osk.registry = wl_display_get_registry (osk.display);
|
||||
wl_registry_add_listener (osk.registry, ®istry_listener, &osk);
|
||||
|
||||
gtk_widget_set_visible(GTK_WIDGET(window), false);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_android_inputmethodservice_InputMethodService_00024ATLInputConnection_nativeSetCompositingText
|
||||
(JNIEnv *env, jobject this, jlong ptr, jstring text, jint newCursorPosition)
|
||||
{
|
||||
const char *data = (*env)->GetStringUTFChars(env, text, NULL);
|
||||
|
||||
INFO("nativeSetCompositingText('%s', cur=%d)\n", data, newCursorPosition);
|
||||
|
||||
if (osk.input_method) {
|
||||
size_t text_len = strlen(data);
|
||||
int cursor;
|
||||
|
||||
if (newCursorPosition > 0)
|
||||
cursor = text_len - newCursorPosition + 1;
|
||||
else
|
||||
cursor = -1 * newCursorPosition;
|
||||
|
||||
zwp_input_method_v2_set_preedit_string (osk.input_method, data, cursor, cursor);
|
||||
strncpy(osk.compositing, data, sizeof(osk.compositing));
|
||||
zwp_input_method_v2_commit (osk.input_method, osk.serial);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_android_inputmethodservice_InputMethodService_00024ATLInputConnection_nativeSetCompositingRegion
|
||||
(JNIEnv *env, jobject this, jlong ptr, jint start, jint end)
|
||||
{
|
||||
INFO("nativeSetCompositingRegion(start=%d, end=%d)\n", start, end);
|
||||
|
||||
if (osk.input_method) {
|
||||
int beforeLength, afterLength, cursor = osk.cursor;
|
||||
char tmp[4096] = {0};
|
||||
|
||||
if (start > end) {
|
||||
int tmp = end;
|
||||
end = start;
|
||||
start = tmp;
|
||||
}
|
||||
|
||||
beforeLength = (end-start);
|
||||
afterLength = 0;
|
||||
|
||||
strncpy(tmp, &osk.surrounding[cursor - beforeLength], beforeLength);
|
||||
cursor = strlen(tmp);
|
||||
|
||||
zwp_input_method_v2_delete_surrounding_text (osk.input_method, beforeLength, afterLength);
|
||||
zwp_input_method_v2_set_preedit_string (osk.input_method, tmp, cursor, cursor);
|
||||
zwp_input_method_v2_commit (osk.input_method, osk.serial);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_android_inputmethodservice_InputMethodService_00024ATLInputConnection_nativeFinishComposingText
|
||||
(JNIEnv *env, jobject this, jlong ptr)
|
||||
{
|
||||
INFO("nativeFinishCompositingText()\n");
|
||||
|
||||
if (osk.input_method) {
|
||||
zwp_input_method_v2_commit_string (osk.input_method, osk.compositing);
|
||||
zwp_input_method_v2_commit (osk.input_method, osk.serial);
|
||||
osk.compositing[0] = '\0';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_android_inputmethodservice_InputMethodService_00024ATLInputConnection_nativeCommitText
|
||||
(JNIEnv *env, jobject this, jlong ptr, jstring text, jint newCursorPosition)
|
||||
{
|
||||
const char *data = (*env)->GetStringUTFChars(env, text, NULL);
|
||||
|
||||
INFO("nativeCommitText('%s', cur=%d)\n", data, newCursorPosition);
|
||||
|
||||
if (osk.input_method) {
|
||||
zwp_input_method_v2_commit_string (osk.input_method, data);
|
||||
zwp_input_method_v2_commit (osk.input_method, osk.serial);
|
||||
osk.compositing[0] = '\0';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_android_inputmethodservice_InputMethodService_00024ATLInputConnection_nativeDeleteSurroundingText
|
||||
(JNIEnv *env, jobject this, jlong ptr, jint beforeLength, jint afterLength)
|
||||
{
|
||||
INFO("nativeDeleteSurroundingText(before=%d, after=%d)\n", beforeLength, afterLength);
|
||||
|
||||
if (osk.input_method) {
|
||||
zwp_input_method_v2_delete_surrounding_text (osk.input_method, beforeLength, afterLength);
|
||||
osk.cursor -= beforeLength;
|
||||
zwp_input_method_v2_commit (osk.input_method, osk.serial);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_android_inputmethodservice_InputMethodService_00024ATLInputConnection_nativeSetSelection
|
||||
(JNIEnv *env, jobject this, jlong ptr, jint start, jint end)
|
||||
{
|
||||
INFO("nativeSetSelection(start=%d, end=%d)\n", start, end);
|
||||
|
||||
if (osk.input_method) {
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_android_inputmethodservice_InputMethodService_00024ATLInputConnection_nativeSendKeyEvent
|
||||
(JNIEnv *env, jobject this, jlong ptr, jlong time, jlong key, jlong state)
|
||||
{
|
||||
INFO("nativeSendKeyEvent(time=%ld, key=%ld, state=%ld)\n", ptr, time, key, state);
|
||||
|
||||
if (key == 67 /* KEYCODE_DEL */ && state == 1 /* ACTION_UP */) {
|
||||
if (osk.input_method) {
|
||||
zwp_input_method_v2_delete_surrounding_text (osk.input_method, 1, 0);
|
||||
zwp_input_method_v2_commit (osk.input_method, osk.serial);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
183
src/api-impl-jni/app/android_app_ATLKeyboardDialog.c
Normal file
183
src/api-impl-jni/app/android_app_ATLKeyboardDialog.c
Normal file
@@ -0,0 +1,183 @@
|
||||
#include <gtk/gtk.h>
|
||||
#include <gtk4-layer-shell/gtk4-layer-shell.h>
|
||||
#include <gio/gio.h>
|
||||
#include <glib.h>
|
||||
#include <jni.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "../defines.h"
|
||||
#include "../util.h"
|
||||
#include "../generated_headers/android_app_ATLKeyboardDialog.h"
|
||||
|
||||
#define DEBUG(fmt, ...) android_log_printf(ANDROID_LOG_INFO, "ATLKeyboardDialog", "%s:%d: " fmt, __func__, __LINE__, ##__VA_ARGS__)
|
||||
|
||||
static GDBusNodeInfo *introspection_data = NULL;
|
||||
static GDBusConnection *dbus_connection = NULL;
|
||||
static gboolean visible = TRUE;
|
||||
static GtkWidget *osk_window = NULL;
|
||||
|
||||
static void emit_property_changed(GDBusConnection *connection)
|
||||
{
|
||||
GVariantBuilder builder;
|
||||
|
||||
g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
|
||||
g_variant_builder_add(&builder, "{sv}", "Visible", g_variant_new_boolean(visible));
|
||||
|
||||
g_dbus_connection_emit_signal(connection,
|
||||
NULL,
|
||||
"/sm/puri/OSK0",
|
||||
"org.freedesktop.DBus.Properties",
|
||||
"PropertiesChanged",
|
||||
g_variant_new("(sa{sv}as)", "sm.puri.OSK0", &builder, NULL), NULL);
|
||||
|
||||
g_variant_builder_clear(&builder);
|
||||
}
|
||||
|
||||
/* Used in IMS. */
|
||||
void atlosk_set_visible(gboolean new_visible)
|
||||
{
|
||||
visible = new_visible;
|
||||
|
||||
if (osk_window) {
|
||||
gtk_widget_set_visible(osk_window, visible);
|
||||
if (dbus_connection)
|
||||
emit_property_changed(dbus_connection);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_method_call(GDBusConnection *connection,
|
||||
const gchar *sender,
|
||||
const gchar *object_path,
|
||||
const gchar *interface_name,
|
||||
const gchar *method_name,
|
||||
GVariant *parameters,
|
||||
GDBusMethodInvocation *invocation,
|
||||
gpointer user_data)
|
||||
{
|
||||
if (g_strcmp0(method_name, "SetVisible") == 0) {
|
||||
gboolean new_visible;
|
||||
|
||||
g_variant_get(parameters, "(b)", &new_visible);
|
||||
atlosk_set_visible(new_visible);
|
||||
g_dbus_method_invocation_return_value(invocation, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static GVariant *handle_get_property(GDBusConnection *connection,
|
||||
const gchar *sender,
|
||||
const gchar *object_path,
|
||||
const gchar *interface_name,
|
||||
const gchar *property_name,
|
||||
GError **error,
|
||||
gpointer user_data)
|
||||
{
|
||||
if (g_strcmp0(property_name, "Visible") == 0) {
|
||||
return g_variant_new_boolean(visible);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const GDBusInterfaceVTable interface_vtable = {
|
||||
handle_method_call,
|
||||
handle_get_property,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static void on_name_acquired(GDBusConnection *connection, const gchar *name, gpointer user_data)
|
||||
{
|
||||
DEBUG("Acquired D-Bus name: %s\n", name);
|
||||
dbus_connection = connection;
|
||||
}
|
||||
|
||||
static int connect_osk_dbus_iface(GtkWidget *dialog)
|
||||
{
|
||||
GDBusConnection *connection;
|
||||
GError *error = NULL;
|
||||
guint registration_id;
|
||||
guint owner_id;
|
||||
|
||||
owner_id = g_bus_own_name(G_BUS_TYPE_SESSION,
|
||||
"sm.puri.OSK0",
|
||||
G_BUS_NAME_OWNER_FLAGS_REPLACE,
|
||||
NULL,
|
||||
on_name_acquired,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
if (owner_id == 0) {
|
||||
g_printerr("OSK: Error: Could not acquire D-Bus name\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* https://world.pages.gitlab.gnome.org/Phosh/phosh/phosh-dbus-sm.puri.OSK0.html */
|
||||
const gchar introspection_xml[] =
|
||||
"<node>"
|
||||
" <interface name='sm.puri.OSK0'>"
|
||||
" <method name='SetVisible'>"
|
||||
" <arg type='b' name='visible' direction='in'/>"
|
||||
" </method>"
|
||||
" <property name='Visible' type='b' access='read'/>"
|
||||
" </interface>"
|
||||
"</node>";
|
||||
|
||||
introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, &error);
|
||||
if (!introspection_data) {
|
||||
g_printerr("OSK: Failed to parse introspection XML: %s\n", error->message);
|
||||
g_error_free(error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
|
||||
if (!connection) {
|
||||
g_printerr("OSK: Failed to connect to D-Bus: %s\n", error->message);
|
||||
g_error_free(error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
registration_id = g_dbus_connection_register_object(connection,
|
||||
"/sm/puri/OSK0",
|
||||
introspection_data->interfaces[0],
|
||||
&interface_vtable,
|
||||
NULL, NULL, &error);
|
||||
if (!registration_id) {
|
||||
g_printerr("OSK: Failed to register object: %s\n", error->message);
|
||||
g_error_free(error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
osk_window = dialog;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static gboolean on_close_request(GtkWidget *dialog, jobject jobj)
|
||||
{
|
||||
JNIEnv *env = get_jni_env();
|
||||
jmethodID dismiss = _METHOD(_CLASS(jobj), "dismiss", "()V");
|
||||
(*env)->CallVoidMethod(env, jobj, dismiss);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL Java_android_app_ATLKeyboardDialog_nativeInit(JNIEnv *env, jobject this)
|
||||
{
|
||||
GtkWidget *dialog = gtk_window_new();
|
||||
GtkWindow *window = GTK_WINDOW(dialog);
|
||||
|
||||
gtk_layer_init_for_window(window);
|
||||
gtk_layer_auto_exclusive_zone_enable(window);
|
||||
gtk_layer_set_namespace(window, "osk");
|
||||
gtk_layer_set_exclusive_zone(window, 200);
|
||||
|
||||
static const gboolean anchors[] = {TRUE, TRUE, FALSE, TRUE};
|
||||
for (int i = 0; i < GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER; i++)
|
||||
gtk_layer_set_anchor(window, i, anchors[i]);
|
||||
|
||||
connect_osk_dbus_iface(dialog);
|
||||
|
||||
gtk_window_set_child(GTK_WINDOW(dialog), gtk_box_new(GTK_ORIENTATION_VERTICAL, 1));
|
||||
g_signal_connect_swapped(dialog, "response", G_CALLBACK(gtk_window_destroy), dialog);
|
||||
g_signal_connect(GTK_WINDOW(dialog), "close-request", G_CALLBACK(on_close_request), _REF(this));
|
||||
|
||||
return _INTPTR(g_object_ref(dialog));
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/* DO NOT EDIT THIS FILE - it is machine generated */
|
||||
#include <jni.h>
|
||||
/* Header for class android_app_ATLKeyboardDialog */
|
||||
|
||||
#ifndef _Included_android_app_ATLKeyboardDialog
|
||||
#define _Included_android_app_ATLKeyboardDialog
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
/*
|
||||
* Class: android_app_ATLKeyboardDialog
|
||||
* Method: nativeInit
|
||||
* Signature: ()J
|
||||
*/
|
||||
JNIEXPORT jlong JNICALL Java_android_app_ATLKeyboardDialog_nativeInit
|
||||
(JNIEnv *, jobject);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -453,14 +453,6 @@ JNIEXPORT void JNICALL Java_android_view_MotionEvent_nativeGetPointerProperties
|
||||
JNIEXPORT void JNICALL Java_android_view_MotionEvent_nativeScale
|
||||
(JNIEnv *, jclass, jint, jfloat);
|
||||
|
||||
/*
|
||||
* Class: android_view_MotionEvent
|
||||
* Method: nativeTransform
|
||||
* Signature: (ILandroid/graphics/Matrix;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL Java_android_view_MotionEvent_nativeTransform
|
||||
(JNIEnv *, jclass, jint, jobject);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user