ATLKeyboard: Integrate with phosh and wayland IME

This commit is contained in:
Nikita Travkin
2025-03-22 00:13:41 +05:00
committed by Mis012
parent 80ec4bd02a
commit 882cd1b471
14 changed files with 1227 additions and 24 deletions

View 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, &registry_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;
}

View 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));
}

View File

@@ -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

View File

@@ -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