Skip to content

Commit

Permalink
Bug 1844905 - Part 3. Basic keyboard support. r=masayuki
Browse files Browse the repository at this point in the history
Implement UIKeyInput protocol to simple basic input.

When implementing UITextInput, we need to consider to share cocoa's code.

Differential Revision: https://phabricator.services.mozilla.com/D184286
  • Loading branch information
glandium committed Mar 4, 2024
1 parent 23ae49e commit e79a84d
Show file tree
Hide file tree
Showing 6 changed files with 434 additions and 7 deletions.
65 changes: 65 additions & 0 deletions widget/uikit/TextInputHandler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef TextInputHandler_h_
#define TextInputHandler_h_

#import <UIKit/UITextInput.h>

#include "mozilla/EventForwards.h"
#include "mozilla/TextEventDispatcherListener.h"
#include "mozilla/widget/IMEData.h"
#include "nsCOMPtr.h"

class nsWindow;

namespace mozilla::widget {
class TextEventDispatcher;

// This is the temporary input class. When implementing UITextInpt protocol, we
// should share this class with Cocoa's version.
class TextInputHandler final : public TextEventDispatcherListener {
public:
TextInputHandler(nsWindow* aWidget);
TextInputHandler() = delete;

NS_DECL_ISUPPORTS

NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
const IMENotification& aNotification) override;
NS_IMETHOD_(IMENotificationRequests) GetIMENotificationRequests() override;
NS_IMETHOD_(void) OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) override;
NS_IMETHOD_(void)
WillDispatchKeyboardEvent(TextEventDispatcher* aTextEventDispatcher,
WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress,
void* aData) override;

// UIKeyInput delegation
bool InsertText(NSString* aText);
bool HandleCommand(Command aCommand);

void OnDestroyed();

private:
virtual ~TextInputHandler() = default;

bool DispatchKeyDownEvent(uint32_t aKeyCode, KeyNameIndex aKeyNameIndex, char16_t aCharCode,
nsEventStatus& aStatus);
bool DispatchKeyUpEvent(uint32_t aKeyCode, KeyNameIndex aKeyNameIndex, char16_t aCharCode,
nsEventStatus& aStatus);
bool DispatchKeyPressEvent(uint32_t aKeyCode, KeyNameIndex aKeyNameIndex, char16_t aCharCode,
nsEventStatus& aStatus);

bool EmulateKeyboardEvent(uint32_t aKeyCode, KeyNameIndex aKeyNameIndex, char16_t charCode);

bool Destroyed() { return !mWidget; }

nsWindow* mWidget; // weak ref
RefPtr<TextEventDispatcher> mDispatcher;
};

} // namespace mozilla::widget
#endif
254 changes: 254 additions & 0 deletions widget/uikit/TextInputHandler.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "TextInputHandler.h"

#import <UIKit/UIKit.h>

#include "mozilla/EventForwards.h"
#include "mozilla/Logging.h"
#include "mozilla/MiscEvents.h"
#include "mozilla/TextEventDispatcher.h"
#include "mozilla/TextEvents.h"
#include "mozilla/WidgetUtils.h"
#include "nsIWidget.h"
#include "nsObjCExceptions.h"
#include "nsString.h"
#include "nsWindow.h"

mozilla::LazyLogModule gIMELog("TextInputHandler");

namespace mozilla::widget {

static void GetStringForNSString(const NSString* aSrc, nsAString& aDist) {
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

if (!aSrc) {
aDist.Truncate();
return;
}

aDist.SetLength([aSrc length]);
[aSrc getCharacters:reinterpret_cast<unichar*>(aDist.BeginWriting())
range:NSMakeRange(0, [aSrc length])];

NS_OBJC_END_TRY_IGNORE_BLOCK;
}

NS_IMPL_ISUPPORTS(TextInputHandler, TextEventDispatcherListener,
nsISupportsWeakReference)

TextInputHandler::TextInputHandler(nsWindow* aWidget)
: mWidget(aWidget), mDispatcher(aWidget->GetTextEventDispatcher()) {}

nsresult TextInputHandler::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
const IMENotification& aNotification) {
return NS_OK;
}

IMENotificationRequests TextInputHandler::GetIMENotificationRequests() {
return IMENotificationRequests();
}

void TextInputHandler::OnRemovedFrom(
TextEventDispatcher* aTextEventDispatcher) {}

void TextInputHandler::WillDispatchKeyboardEvent(
TextEventDispatcher* aTextEventDispatcher,
WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress,
void* aData) {}

bool TextInputHandler::InsertText(NSString* aText) {
nsString str;
GetStringForNSString(aText, str);

MOZ_LOG(gIMELog, LogLevel::Info,
("%p TextInputHandler::InsertText(aText=%s)", this,
NS_ConvertUTF16toUTF8(str).get()));

if (Destroyed()) {
return false;
}

if (str.Length() == 1) {
char16_t charCode = str[0];
if (charCode == 0x0a) {
return EmulateKeyboardEvent(NS_VK_RETURN, KEY_NAME_INDEX_Enter, charCode);
}
if (charCode == 0x08) {
return EmulateKeyboardEvent(NS_VK_BACK, KEY_NAME_INDEX_Backspace,
charCode);
}
if (uint32_t keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode)) {
return EmulateKeyboardEvent(keyCode, KEY_NAME_INDEX_USE_STRING, charCode);
}
}

nsEventStatus status = nsEventStatus_eIgnore;
RefPtr<nsWindow> widget(mWidget);
if (!DispatchKeyDownEvent(NS_VK_PROCESSKEY, KEY_NAME_INDEX_Process, 0,
status)) {
return false;
}
if (Destroyed()) {
return false;
}

mDispatcher->CommitComposition(status, &str, nullptr);
if (widget->Destroyed()) {
return false;
}

DispatchKeyUpEvent(NS_VK_PROCESSKEY, KEY_NAME_INDEX_Process, 0, status);

return true;
}

bool TextInputHandler::HandleCommand(Command aCommand) {
MOZ_LOG(gIMELog, LogLevel::Info,
("%p TextInputHandler::HandleCommand, aCommand=%s", this,
ToChar(aCommand)));

if (Destroyed()) {
return false;
}

if (aCommand != Command::DeleteCharBackward) {
return false;
}

nsEventStatus status = nsEventStatus_eIgnore;
if (!DispatchKeyDownEvent(NS_VK_BACK, KEY_NAME_INDEX_Backspace, 0, status)) {
return true;
}
if (Destroyed() || status == nsEventStatus_eConsumeNoDefault) {
return true;
}

// TODO: Focus check

if (!DispatchKeyPressEvent(NS_VK_BACK, KEY_NAME_INDEX_Backspace, 0, status)) {
return true;
}
if (Destroyed() || status == nsEventStatus_eConsumeNoDefault) {
return true;
}

// TODO: Focus check

DispatchKeyUpEvent(NS_VK_BACK, KEY_NAME_INDEX_Backspace, 0, status);

return true;
}

static uint32_t ComputeKeyModifiers(uint32_t aCharCode) {
if (aCharCode >= 'A' && aCharCode <= 'Z') {
return MODIFIER_SHIFT;
}
return 0;
}

static void InitKeyEvent(WidgetKeyboardEvent& aEvent, uint32_t aKeyCode,
KeyNameIndex aKeyNameIndex, char16_t aCharCode) {
aEvent.mKeyCode = aKeyCode;
aEvent.mIsRepeat = false;
aEvent.mKeyNameIndex = aKeyNameIndex;
// TODO(m_kato):
// How to get native key? Then, implement NativeKeyToDOM*.h for iOS
aEvent.mCodeNameIndex = CODE_NAME_INDEX_UNKNOWN;
if (aEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) {
aEvent.mKeyValue = aCharCode;
}
aEvent.mModifiers = ComputeKeyModifiers(aCharCode);
aEvent.mLocation = eKeyLocationStandard;
aEvent.mTimeStamp = TimeStamp::Now();
}

bool TextInputHandler::DispatchKeyDownEvent(uint32_t aKeyCode,
KeyNameIndex aKeyNameIndex,
char16_t aCharCode,
nsEventStatus& aStatus) {
MOZ_ASSERT(aKeyCode);
MOZ_ASSERT(mWidget);

WidgetKeyboardEvent keydownEvent(true, eKeyDown, mWidget);
InitKeyEvent(keydownEvent, aKeyCode, aKeyNameIndex, aCharCode);
nsresult rv = mDispatcher->BeginNativeInputTransaction();
if (NS_FAILED(rv)) {
NS_WARNING("BeginNativeInputTransaction is failed");
return false;
}
return mDispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent, aStatus);
}

bool TextInputHandler::DispatchKeyUpEvent(uint32_t aKeyCode,
KeyNameIndex aKeyNameIndex,
char16_t aCharCode,
nsEventStatus& aStatus) {
MOZ_ASSERT(aKeyCode);
MOZ_ASSERT(mWidget);

WidgetKeyboardEvent keyupEvent(true, eKeyUp, mWidget);
InitKeyEvent(keyupEvent, aKeyCode, aKeyNameIndex, aCharCode);
nsresult rv = mDispatcher->BeginNativeInputTransaction();
if (NS_FAILED(rv)) {
NS_WARNING("BeginNativeInputTransaction is failed");
return false;
}
return mDispatcher->DispatchKeyboardEvent(eKeyUp, keyupEvent, aStatus);
}

bool TextInputHandler::DispatchKeyPressEvent(uint32_t aKeyCode,
KeyNameIndex aKeyNameIndex,
char16_t aCharCode,
nsEventStatus& aStatus) {
MOZ_ASSERT(aKeyCode);
MOZ_ASSERT(mWidget);

WidgetKeyboardEvent keypressEvent(true, eKeyPress, mWidget);
InitKeyEvent(keypressEvent, aKeyCode, aKeyNameIndex, aCharCode);
nsresult rv = mDispatcher->BeginNativeInputTransaction();
if (NS_FAILED(rv)) {
NS_WARNING("BeginNativeInputTransaction is failed");
return false;
}
return mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, aStatus);
}

bool TextInputHandler::EmulateKeyboardEvent(uint32_t aKeyCode,
KeyNameIndex aKeyNameIndex,
char16_t aCharCode) {
MOZ_ASSERT(aCharCode);

MOZ_LOG(gIMELog, LogLevel::Info,
("%p TextInputHandler::EmulateKeyboardEvent(aKeyCode=%x, "
"aKeyNameIndex=%x, aCharCode=%x)",
this, aKeyCode, aKeyNameIndex, aCharCode));

nsEventStatus status = nsEventStatus_eIgnore;
if (!DispatchKeyDownEvent(aKeyCode, aKeyNameIndex, aCharCode, status)) {
return true;
}
if (Destroyed() || status == nsEventStatus_eConsumeNoDefault) {
return true;
}
// TODO: Focus check

if (!DispatchKeyPressEvent(aKeyCode, aKeyNameIndex, aCharCode, status)) {
return true;
}
if (Destroyed() || status == nsEventStatus_eConsumeNoDefault) {
return true;
}
// TODO: Focus check

DispatchKeyUpEvent(aKeyCode, aKeyNameIndex, aCharCode, status);
return true;
}

void TextInputHandler::OnDestroyed() { mWidget = nullptr; }

} // namespace mozilla::widget
4 changes: 4 additions & 0 deletions widget/uikit/moz.build
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@
with Files("**"):
BUG_COMPONENT = ("Core", "Widget")

with Files("*TextInput*"):
BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")

SOURCES += [
"GfxInfo.cpp",
"nsAppShell.mm",
"nsLookAndFeel.mm",
"nsWidgetFactory.mm",
"nsWindow.mm",
"TextInputHandler.mm",
]

include("/ipc/chromium/chromium-config.mozbuild")
Expand Down
15 changes: 14 additions & 1 deletion widget/uikit/nsWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
@class UIView;
@class ChildView;

namespace mozilla::widget {
class TextInputHandler;
}

class nsWindow final : public nsBaseWidget {
typedef nsBaseWidget Inherited;

Expand Down Expand Up @@ -79,6 +83,13 @@ class nsWindow final : public nsBaseWidget {
void SetInputContext(const InputContext& aContext,
const InputContextAction& aAction) override;
InputContext GetInputContext() override;
TextEventDispatcherListener* GetNativeTextEventDispatcherListener() override;

mozilla::widget::TextInputHandler* GetTextInputHandler() const {
return mTextInputHandler;
}
bool IsVirtualKeyboardDisabled() const;

/*
virtual bool ExecuteNativeKeyBinding(
NativeKeyBindingsType aType,
Expand All @@ -102,7 +113,9 @@ class nsWindow final : public nsBaseWidget {
nsSizeMode mSizeMode;
nsTArray<nsWindow*> mChildren;
nsWindow* mParent;
InputContext mInputContext;

mozilla::widget::InputContext mInputContext;
RefPtr<mozilla::widget::TextInputHandler> mTextInputHandler;

void OnSizeChanged(const mozilla::gfx::IntSize& aSize);

Expand Down
Loading

0 comments on commit e79a84d

Please sign in to comment.