blob: 9d4b46d5298d4709f6c39091dcea5f532b2c3821 [file] [log] [blame]
/*
* Copyright (C) 2019 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "WebKitInputMethodContextImplGtk.h"
#include <gtk/gtk.h>
#include <wtf/MathExtras.h>
#include <wtf/glib/GRefPtr.h>
#include <wtf/glib/WTFGType.h>
#include <wtf/text/CString.h>
struct _WebKitInputMethodContextImplGtkPrivate {
GRefPtr<GtkIMContext> context;
CString surroundingText;
unsigned surroundingCursorIndex;
};
WEBKIT_DEFINE_TYPE(WebKitInputMethodContextImplGtk, webkit_input_method_context_impl_gtk, WEBKIT_TYPE_INPUT_METHOD_CONTEXT)
static GtkInputPurpose toGtkInputPurpose(WebKitInputPurpose purpose)
{
switch (purpose) {
case WEBKIT_INPUT_PURPOSE_FREE_FORM:
return GTK_INPUT_PURPOSE_FREE_FORM;
case WEBKIT_INPUT_PURPOSE_DIGITS:
return GTK_INPUT_PURPOSE_DIGITS;
case WEBKIT_INPUT_PURPOSE_NUMBER:
return GTK_INPUT_PURPOSE_NUMBER;
case WEBKIT_INPUT_PURPOSE_PHONE:
return GTK_INPUT_PURPOSE_PHONE;
case WEBKIT_INPUT_PURPOSE_URL:
return GTK_INPUT_PURPOSE_URL;
case WEBKIT_INPUT_PURPOSE_EMAIL:
return GTK_INPUT_PURPOSE_EMAIL;
case WEBKIT_INPUT_PURPOSE_PASSWORD:
return GTK_INPUT_PURPOSE_PASSWORD;
}
RELEASE_ASSERT_NOT_REACHED();
}
static GtkInputHints toGtkInputHints(WebKitInputHints hints)
{
unsigned gtkHints = 0;
if (hints & WEBKIT_INPUT_HINT_SPELLCHECK)
gtkHints |= GTK_INPUT_HINT_SPELLCHECK;
if (hints & WEBKIT_INPUT_HINT_LOWERCASE)
gtkHints |= GTK_INPUT_HINT_LOWERCASE;
if (hints & WEBKIT_INPUT_HINT_UPPERCASE_CHARS)
gtkHints |= GTK_INPUT_HINT_UPPERCASE_CHARS;
if (hints & WEBKIT_INPUT_HINT_UPPERCASE_WORDS)
gtkHints |= GTK_INPUT_HINT_UPPERCASE_WORDS;
if (hints & WEBKIT_INPUT_HINT_UPPERCASE_SENTENCES)
gtkHints |= GTK_INPUT_HINT_UPPERCASE_SENTENCES;
if (hints & WEBKIT_INPUT_HINT_INHIBIT_OSK)
gtkHints |= GTK_INPUT_HINT_INHIBIT_OSK;
return static_cast<GtkInputHints>(gtkHints);
}
static void inputPurposeChangedCallback(WebKitInputMethodContextImplGtk* context)
{
g_object_set(context->priv->context.get(), "input-purpose", toGtkInputPurpose(webkit_input_method_context_get_input_purpose(WEBKIT_INPUT_METHOD_CONTEXT(context))), nullptr);
}
static void inputHintsChangedCallback(WebKitInputMethodContextImplGtk* context)
{
g_object_set(context->priv->context.get(), "input-hints", toGtkInputHints(webkit_input_method_context_get_input_hints(WEBKIT_INPUT_METHOD_CONTEXT(context))), nullptr);
}
static void contextPreeditStartCallback(WebKitInputMethodContextImplGtk* context)
{
g_signal_emit_by_name(context, "preedit-started", nullptr);
}
static void contextPreeditChangedCallback(WebKitInputMethodContextImplGtk* context)
{
g_signal_emit_by_name(context, "preedit-changed", nullptr);
}
static void contextPreeditEndCallback(WebKitInputMethodContextImplGtk* context)
{
g_signal_emit_by_name(context, "preedit-finished", nullptr);
}
static void contextCommitCallback(WebKitInputMethodContextImplGtk* context, const char* text)
{
g_signal_emit_by_name(context, "committed", text, nullptr);
}
static gboolean contextRetrieveSurrounding(WebKitInputMethodContextImplGtk* context)
{
auto* priv = context->priv;
gtk_im_context_set_surrounding(priv->context.get(), priv->surroundingText.data(), priv->surroundingText.length(), priv->surroundingCursorIndex);
return TRUE;
}
static void webkitInputMethodContextImplGtkConstructed(GObject* object)
{
G_OBJECT_CLASS(webkit_input_method_context_impl_gtk_parent_class)->constructed(object);
g_signal_connect_swapped(object, "notify::input-purpose", G_CALLBACK(inputPurposeChangedCallback), object);
g_signal_connect_swapped(object, "notify::input-hints", G_CALLBACK(inputHintsChangedCallback), object);
auto* priv = WEBKIT_INPUT_METHOD_CONTEXT_IMPL_GTK(object)->priv;
priv->context = adoptGRef(gtk_im_multicontext_new());
g_signal_connect_object(priv->context.get(), "preedit-start", G_CALLBACK(contextPreeditStartCallback), object, G_CONNECT_SWAPPED);
g_signal_connect_object(priv->context.get(), "preedit-changed", G_CALLBACK(contextPreeditChangedCallback), object, G_CONNECT_SWAPPED);
g_signal_connect_object(priv->context.get(), "preedit-end", G_CALLBACK(contextPreeditEndCallback), object, G_CONNECT_SWAPPED);
g_signal_connect_object(priv->context.get(), "commit", G_CALLBACK(contextCommitCallback), object, G_CONNECT_SWAPPED);
g_signal_connect_object(priv->context.get(), "retrieve-surrounding", G_CALLBACK(contextRetrieveSurrounding), object, G_CONNECT_SWAPPED);
}
static void webkitInputMethodContextImplGtkSetEnablePreedit(WebKitInputMethodContext* context, gboolean enabled)
{
auto* priv = WEBKIT_INPUT_METHOD_CONTEXT_IMPL_GTK(context)->priv;
gtk_im_context_set_use_preedit(priv->context.get(), enabled);
}
static void webkitInputMethodContextImplGtkGetPreedit(WebKitInputMethodContext* context, char** text, GList** underlines, guint* cursorOffset)
{
auto* priv = WEBKIT_INPUT_METHOD_CONTEXT_IMPL_GTK(context)->priv;
PangoAttrList* attrList = nullptr;
int offset;
gtk_im_context_get_preedit_string(priv->context.get(), text, underlines ? &attrList : nullptr, &offset);
if (underlines) {
*underlines = nullptr;
if (attrList) {
PangoAttrIterator* iter = pango_attr_list_get_iterator(attrList);
do {
if (!pango_attr_iterator_get(iter, PANGO_ATTR_UNDERLINE))
continue;
int start, end;
pango_attr_iterator_range(iter, &start, &end);
auto* underline = webkit_input_method_underline_new(clampTo<unsigned>(start), clampTo<unsigned>(end));
if (auto* colorAttribute = pango_attr_iterator_get(iter, PANGO_ATTR_UNDERLINE_COLOR)) {
PangoColor* color = &(reinterpret_cast<PangoAttrColor*>(colorAttribute))->color;
GdkRGBA rgba = { color->red / 65535.f, color->green / 65535.f, color->blue / 65535.f, 1.f };
webkit_input_method_underline_set_color(underline, &rgba);
}
*underlines = g_list_prepend(*underlines, underline);
} while (pango_attr_iterator_next(iter));
}
}
if (cursorOffset)
*cursorOffset = clampTo<unsigned>(offset);
}
#if USE(GTK4)
static gboolean webkitInputMethodContextImplGtkFilterKeyEvent(WebKitInputMethodContext* context, GdkEvent* keyEvent)
#else
static gboolean webkitInputMethodContextImplGtkFilterKeyEvent(WebKitInputMethodContext* context, GdkEventKey* keyEvent)
#endif
{
auto* priv = WEBKIT_INPUT_METHOD_CONTEXT_IMPL_GTK(context)->priv;
return gtk_im_context_filter_keypress(priv->context.get(), keyEvent);
}
static void webkitInputMethodContextImplGtkNotifyFocusIn(WebKitInputMethodContext* context)
{
auto* priv = WEBKIT_INPUT_METHOD_CONTEXT_IMPL_GTK(context)->priv;
gtk_im_context_focus_in(priv->context.get());
}
static void webkitInputMethodContextImplGtkNotifyFocusOut(WebKitInputMethodContext* context)
{
auto* priv = WEBKIT_INPUT_METHOD_CONTEXT_IMPL_GTK(context)->priv;
gtk_im_context_focus_out(priv->context.get());
}
static void webkitInputMethodContextImplGtkNotifyCursorArea(WebKitInputMethodContext* context, int x, int y, int width, int height)
{
auto* priv = WEBKIT_INPUT_METHOD_CONTEXT_IMPL_GTK(context)->priv;
GdkRectangle cursorRect = { x, y, width, height };
gtk_im_context_set_cursor_location(priv->context.get(), &cursorRect);
}
static void webkitInputMethodContextImplGtkNotifySurrounding(WebKitInputMethodContext* context, const gchar* text, unsigned length, unsigned cursorIndex, unsigned)
{
auto* priv = WEBKIT_INPUT_METHOD_CONTEXT_IMPL_GTK(context)->priv;
priv->surroundingText = { text, length };
priv->surroundingCursorIndex = cursorIndex;
}
static void webkitInputMethodContextImplGtkReset(WebKitInputMethodContext* context)
{
auto* priv = WEBKIT_INPUT_METHOD_CONTEXT_IMPL_GTK(context)->priv;
gtk_im_context_reset(priv->context.get());
}
static void webkit_input_method_context_impl_gtk_class_init(WebKitInputMethodContextImplGtkClass* klass)
{
GObjectClass* objectClass = G_OBJECT_CLASS(klass);
objectClass->constructed = webkitInputMethodContextImplGtkConstructed;
auto* imClass = WEBKIT_INPUT_METHOD_CONTEXT_CLASS(klass);
imClass->set_enable_preedit = webkitInputMethodContextImplGtkSetEnablePreedit;
imClass->get_preedit = webkitInputMethodContextImplGtkGetPreedit;
imClass->filter_key_event = webkitInputMethodContextImplGtkFilterKeyEvent;
imClass->notify_focus_in = webkitInputMethodContextImplGtkNotifyFocusIn;
imClass->notify_focus_out = webkitInputMethodContextImplGtkNotifyFocusOut;
imClass->notify_cursor_area = webkitInputMethodContextImplGtkNotifyCursorArea;
imClass->notify_surrounding = webkitInputMethodContextImplGtkNotifySurrounding;
imClass->reset = webkitInputMethodContextImplGtkReset;
}
WebKitInputMethodContext* webkitInputMethodContextImplGtkNew()
{
return WEBKIT_INPUT_METHOD_CONTEXT(g_object_new(WEBKIT_TYPE_INPUT_METHOD_CONTEXT_IMPL_GTK, nullptr));
}
void webkitInputMethodContextImplGtkSetClientWidget(WebKitInputMethodContextImplGtk* context, GtkWidget* widget)
{
#if USE(GTK4)
gtk_im_context_set_client_widget(context->priv->context.get(), widget);
#else
gtk_im_context_set_client_window(context->priv->context.get(), widget ? gtk_widget_get_window(widget) : nullptr);
#endif
}