InputMethodFilter.cpp [plain text]
#include "config.h"
#include "InputMethodFilter.h"
#include "WebKitInputMethodContextPrivate.h"
#include "WebKitWebViewPrivate.h"
#include "WebPageProxy.h"
#include <WebCore/PlatformDisplay.h>
#include <wtf/SetForScope.h>
namespace WebKit {
using namespace WebCore;
InputMethodFilter::~InputMethodFilter()
{
setContext(nullptr);
}
void InputMethodFilter::preeditStartedCallback(InputMethodFilter* filter)
{
filter->preeditStarted();
}
void InputMethodFilter::preeditChangedCallback(InputMethodFilter* filter)
{
filter->preeditChanged();
}
void InputMethodFilter::preeditFinishedCallback(InputMethodFilter* filter)
{
filter->preeditFinished();
}
void InputMethodFilter::committedCallback(InputMethodFilter* filter, const char* compositionString)
{
filter->committed(compositionString);
}
void InputMethodFilter::setContext(WebKitInputMethodContext* context)
{
if (m_context) {
webkitInputMethodContextSetWebView(m_context.get(), nullptr);
g_signal_handlers_disconnect_matched(m_context.get(), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, this);
}
m_context = context;
if (!m_context)
return;
ASSERT(webkitInputMethodContextGetWebView(m_context.get()));
g_signal_connect_swapped(m_context.get(), "preedit-started", G_CALLBACK(preeditStartedCallback), this);
g_signal_connect_swapped(m_context.get(), "preedit-changed", G_CALLBACK(preeditChangedCallback), this);
g_signal_connect_swapped(m_context.get(), "preedit-finished", G_CALLBACK(preeditFinishedCallback), this);
g_signal_connect_swapped(m_context.get(), "committed", G_CALLBACK(committedCallback), this);
if (m_enabled && isViewFocused())
notifyFocusedIn();
}
void InputMethodFilter::setEnabled(bool enabled)
{
if (!enabled)
notifyFocusedOut();
m_enabled = enabled;
if (m_enabled && isViewFocused())
notifyFocusedIn();
}
InputMethodFilter::FilterResult InputMethodFilter::filterKeyEvent(PlatformEventKey* keyEvent)
{
if (!m_enabled || !m_context)
return { };
SetForScope<bool> filteringContextIsAcive(m_filteringContext.isActive, true);
m_filteringContext.preeditChanged = false;
m_compositionResult = { };
bool handled = webkit_input_method_context_filter_key_event(m_context.get(), keyEvent);
if (!handled)
return { };
if (!m_filteringContext.preeditChanged && m_compositionResult.length() == 1)
return { false, WTFMove(m_compositionResult) };
if (!platformEventKeyIsKeyPress(keyEvent))
return { };
return { true, { } };
}
bool InputMethodFilter::isViewFocused() const
{
if (!m_enabled || !m_context)
return false;
#if ENABLE(DEVELOPER_MODE) && PLATFORM(X11)
if (PlatformDisplay::sharedDisplay().type() == PlatformDisplay::Type::X11) {
if (!g_strcmp0(g_getenv("UNDER_XVFB"), "yes"))
return true;
}
#endif
auto* webView = webkitInputMethodContextGetWebView(m_context.get());
ASSERT(webView);
return webkitWebViewGetPage(webView).isViewFocused();
}
void InputMethodFilter::notifyFocusedIn()
{
if (!m_enabled || !m_context)
return;
webkit_input_method_context_notify_focus_in(m_context.get());
}
void InputMethodFilter::notifyFocusedOut()
{
if (!m_enabled || !m_context)
return;
cancelComposition();
webkit_input_method_context_notify_focus_out(m_context.get());
}
void InputMethodFilter::notifyCursorRect(const IntRect& cursorRect)
{
if (!m_enabled || !m_context)
return;
static const int windowMovementThreshold = 10 * 10;
if (cursorRect.location().distanceSquaredToPoint(m_cursorLocation) < windowMovementThreshold)
return;
m_cursorLocation = cursorRect.location();
auto translatedRect = platformTransformCursorRectToViewCoordinates(cursorRect);
webkit_input_method_context_notify_cursor_area(m_context.get(), translatedRect.x(), translatedRect.y(), translatedRect.width(), translatedRect.height());
}
void InputMethodFilter::preeditStarted()
{
if (!m_enabled)
return;
if (m_filteringContext.isActive)
m_filteringContext.preeditChanged = true;
m_preedit = { };
}
void InputMethodFilter::preeditChanged()
{
if (!m_enabled)
return;
if (m_filteringContext.isActive)
m_filteringContext.preeditChanged = true;
GUniqueOutPtr<gchar> newPreedit;
GList* underlines = nullptr;
unsigned cursorOffset;
webkit_input_method_context_get_preedit(m_context.get(), &newPreedit.outPtr(), &underlines, &cursorOffset);
if (m_preedit.text.utf8() == newPreedit.get()) {
g_list_free_full(underlines, reinterpret_cast<GDestroyNotify>(webkit_input_method_underline_free));
return;
}
m_preedit.text = String::fromUTF8(newPreedit.get());
m_preedit.cursorOffset = std::min(cursorOffset, m_preedit.text.length());
if (underlines) {
for (auto* it = underlines; it; it = g_list_next(it)) {
auto* underline = static_cast<WebKitInputMethodUnderline*>(it->data);
m_preedit.underlines.append(webkitInputMethodUnderlineGetCompositionUnderline(underline));
}
g_list_free_full(underlines, reinterpret_cast<GDestroyNotify>(webkit_input_method_underline_free));
} else
m_preedit.underlines.append(CompositionUnderline(0, m_preedit.text.length(), CompositionUnderlineColor::TextColor, Color(Color::black), false));
auto* webView = webkitInputMethodContextGetWebView(m_context.get());
ASSERT(webView);
webkitWebViewSetComposition(webView, m_preedit.text, m_preedit.underlines, EditingRange(m_preedit.cursorOffset, 1));
}
void InputMethodFilter::preeditFinished()
{
if (!m_enabled)
return;
if (m_filteringContext.isActive)
m_filteringContext.preeditChanged = true;
bool wasEmpty = m_preedit.text.isEmpty();
m_preedit = { };
if (wasEmpty)
return;
auto* webView = webkitInputMethodContextGetWebView(m_context.get());
ASSERT(webView);
webkitWebViewSetComposition(webView, { }, { }, EditingRange(0, 1));
}
void InputMethodFilter::committed(const char* compositionString)
{
if (!m_enabled)
return;
m_compositionResult = String::fromUTF8(compositionString);
bool preeditWasEmpty = m_preedit.text.isEmpty();
m_preedit = { };
auto* webView = webkitInputMethodContextGetWebView(m_context.get());
ASSERT(webView);
if (m_filteringContext.isActive) {
if (!m_filteringContext.preeditChanged && preeditWasEmpty && m_compositionResult.length() == 1)
return;
}
webkitWebViewConfirmComposition(webView, m_compositionResult);
m_compositionResult = { };
}
void InputMethodFilter::cancelComposition()
{
if (m_preedit.text.isNull())
return;
auto* webView = webkitInputMethodContextGetWebView(m_context.get());
ASSERT(webView);
webkitWebViewCancelComposition(webView, m_preedit.text);
m_preedit = { };
m_compositionResult = { };
webkit_input_method_context_reset(m_context.get());
}
}