summaryrefslogtreecommitdiffstats
path: root/widget/windows/IMMHandler.h
diff options
context:
space:
mode:
Diffstat (limited to 'widget/windows/IMMHandler.h')
-rw-r--r--widget/windows/IMMHandler.h495
1 files changed, 495 insertions, 0 deletions
diff --git a/widget/windows/IMMHandler.h b/widget/windows/IMMHandler.h
new file mode 100644
index 000000000..f3120cfec
--- /dev/null
+++ b/widget/windows/IMMHandler.h
@@ -0,0 +1,495 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 IMMHandler_h_
+#define IMMHandler_h_
+
+#include "nscore.h"
+#include <windows.h>
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsIWidget.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "nsRect.h"
+#include "WritingModes.h"
+#include "npapi.h"
+
+class nsWindow;
+class nsWindowBase;
+
+namespace mozilla {
+namespace widget {
+
+struct MSGResult;
+
+class IMEContext final
+{
+public:
+ IMEContext()
+ : mWnd(nullptr)
+ , mIMC(nullptr)
+ {
+ }
+
+ explicit IMEContext(HWND aWnd);
+ explicit IMEContext(nsWindowBase* aWindowBase);
+
+ ~IMEContext()
+ {
+ Clear();
+ }
+
+ HIMC get() const
+ {
+ return mIMC;
+ }
+
+ void Init(HWND aWnd);
+ void Init(nsWindowBase* aWindowBase);
+ void Clear();
+
+ bool IsValid() const
+ {
+ return !!mIMC;
+ }
+
+ void SetOpenState(bool aOpen) const
+ {
+ if (!mIMC) {
+ return;
+ }
+ ::ImmSetOpenStatus(mIMC, aOpen);
+ }
+
+ bool GetOpenState() const
+ {
+ if (!mIMC) {
+ return false;
+ }
+ return (::ImmGetOpenStatus(mIMC) != FALSE);
+ }
+
+ bool AssociateDefaultContext()
+ {
+ // We assume that there is only default IMC, no new IMC has been created.
+ if (mIMC) {
+ return false;
+ }
+ if (!::ImmAssociateContextEx(mWnd, nullptr, IACE_DEFAULT)) {
+ return false;
+ }
+ mIMC = ::ImmGetContext(mWnd);
+ return (mIMC != nullptr);
+ }
+
+ bool Disassociate()
+ {
+ if (!mIMC) {
+ return false;
+ }
+ if (!::ImmAssociateContextEx(mWnd, nullptr, 0)) {
+ return false;
+ }
+ ::ImmReleaseContext(mWnd, mIMC);
+ mIMC = nullptr;
+ return true;
+ }
+
+protected:
+ IMEContext(const IMEContext& aOther)
+ {
+ MOZ_CRASH("Don't copy IMEContext");
+ }
+
+ HWND mWnd;
+ HIMC mIMC;
+};
+
+class IMMHandler final
+{
+public:
+ static void Initialize();
+ static void Terminate();
+
+ // If Process*() returns true, the caller shouldn't do anything anymore.
+ static bool ProcessMessage(nsWindow* aWindow, UINT msg,
+ WPARAM& wParam, LPARAM& lParam,
+ MSGResult& aResult);
+ static bool IsComposing()
+ {
+ return IsComposingOnOurEditor();
+ }
+ static bool IsComposingOn(nsWindow* aWindow)
+ {
+ return IsComposing() && IsComposingWindow(aWindow);
+ }
+
+#ifdef DEBUG
+ /**
+ * IsIMEAvailable() returns TRUE when current keyboard layout has IME.
+ * Otherwise, FALSE.
+ */
+ static bool IsIMEAvailable() { return !!::ImmIsIME(::GetKeyboardLayout(0)); }
+#endif
+
+ // If aForce is TRUE, these methods doesn't check whether we have composition
+ // or not. If you don't set it to TRUE, these method doesn't commit/cancel
+ // the composition on uexpected window.
+ static void CommitComposition(nsWindow* aWindow, bool aForce = false);
+ static void CancelComposition(nsWindow* aWindow, bool aForce = false);
+ static void OnFocusChange(bool aFocus, nsWindow* aWindow);
+ static void OnUpdateComposition(nsWindow* aWindow);
+ static void OnSelectionChange(nsWindow* aWindow,
+ const IMENotification& aIMENotification,
+ bool aIsIMMActive);
+
+ static nsIMEUpdatePreference GetIMEUpdatePreference();
+
+ // Returns NS_SUCCESS_EVENT_CONSUMED if the mouse button event is consumed by
+ // IME. Otherwise, NS_OK.
+ static nsresult OnMouseButtonEvent(nsWindow* aWindow,
+ const IMENotification& aIMENotification);
+ static void SetCandidateWindow(nsWindow* aWindow, CANDIDATEFORM* aForm);
+ static void DefaultProcOfPluginEvent(nsWindow* aWindow,
+ const NPEvent* aEvent);
+
+protected:
+ static void EnsureHandlerInstance();
+
+ static bool IsComposingOnOurEditor();
+ static bool IsComposingOnPlugin();
+ static bool IsComposingWindow(nsWindow* aWindow);
+
+ static bool IsJapanist2003Active();
+ static bool IsGoogleJapaneseInputActive();
+
+ static bool ShouldDrawCompositionStringOurselves();
+ static bool IsVerticalWritingSupported();
+ // aWindow can be nullptr if it's called without receiving WM_INPUTLANGCHANGE.
+ static void InitKeyboardLayout(nsWindow* aWindow, HKL aKeyboardLayout);
+ static UINT GetKeyboardCodePage();
+
+ /**
+ * Checks whether the window is top level window of the composing window.
+ * In this method, the top level window means in all windows, not only in all
+ * OUR windows. I.e., if the aWindow is embedded, this always returns FALSE.
+ */
+ static bool IsTopLevelWindowOfComposition(nsWindow* aWindow);
+
+ static bool ProcessInputLangChangeMessage(nsWindow* aWindow,
+ WPARAM wParam,
+ LPARAM lParam,
+ MSGResult& aResult);
+ static bool ProcessMessageForPlugin(nsWindow* aWindow, UINT msg,
+ WPARAM &wParam, LPARAM &lParam,
+ bool &aRet, MSGResult& aResult);
+
+ IMMHandler();
+ ~IMMHandler();
+
+ // On*() methods return true if the caller of message handler shouldn't do
+ // anything anymore. Otherwise, false.
+ static bool OnKeyDownEvent(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+
+ bool OnIMEStartComposition(nsWindow* aWindow, MSGResult& aResult);
+ void OnIMEStartCompositionOnPlugin(nsWindow* aWindow,
+ WPARAM wParam, LPARAM lParam);
+ bool OnIMEComposition(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ void OnIMECompositionOnPlugin(nsWindow* aWindow, WPARAM wParam,
+ LPARAM lParam);
+ bool OnIMEEndComposition(nsWindow* aWindow, MSGResult& aResult);
+ void OnIMEEndCompositionOnPlugin(nsWindow* aWindow, WPARAM wParam,
+ LPARAM lParam);
+ bool OnIMERequest(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ bool OnIMECharOnPlugin(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ bool OnChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ bool OnCharOnPlugin(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ void OnInputLangChange(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+
+ // These message handlers don't use instance members, we should not create
+ // the instance by the messages. So, they should be static.
+ static bool OnIMEChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ static bool OnIMESetContext(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ static bool OnIMESetContextOnPlugin(nsWindow* aWindow,
+ WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ static bool OnIMECompositionFull(nsWindow* aWindow, MSGResult& aResult);
+ static bool OnIMENotify(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+ static bool OnIMESelect(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
+ MSGResult& aResult);
+
+ // The result of Handle* method mean "Processed" when it's TRUE.
+ void HandleStartComposition(nsWindow* aWindow,
+ const IMEContext& aContext);
+ bool HandleComposition(nsWindow* aWindow,
+ const IMEContext& aContext,
+ LPARAM lParam);
+ // If aCommitString is null, this commits composition with the latest
+ // dispatched data. Otherwise, commits composition with the value.
+ void HandleEndComposition(nsWindow* aWindow,
+ const nsAString* aCommitString = nullptr);
+ bool HandleReconvert(nsWindow* aWindow, LPARAM lParam, LRESULT *oResult);
+ bool HandleQueryCharPosition(nsWindow* aWindow, LPARAM lParam,
+ LRESULT *oResult);
+ bool HandleDocumentFeed(nsWindow* aWindow, LPARAM lParam, LRESULT *oResult);
+
+ /**
+ * When a window's IME context is activating but we have composition on
+ * another window, we should commit our composition because IME context is
+ * shared by all our windows (including plug-ins).
+ * @param aWindow is a new activated window.
+ * If aWindow is our composing window, this method does nothing.
+ * Otherwise, this commits the composition on the previous window.
+ * If this method did commit a composition, this returns TRUE.
+ */
+ bool CommitCompositionOnPreviousWindow(nsWindow* aWindow);
+
+ /**
+ * ResolveIMECaretPos
+ * Convert the caret rect of a composition event to another widget's
+ * coordinate system.
+ *
+ * @param aReferenceWidget The origin widget of aCursorRect.
+ * Typically, this is mReferenceWidget of the
+ * composing events. If the aCursorRect is in screen
+ * coordinates, set nullptr.
+ * @param aCursorRect The cursor rect.
+ * @param aNewOriginWidget aOutRect will be in this widget's coordinates. If
+ * this is nullptr, aOutRect will be in screen
+ * coordinates.
+ * @param aOutRect The converted cursor rect.
+ */
+ void ResolveIMECaretPos(nsIWidget* aReferenceWidget,
+ mozilla::LayoutDeviceIntRect& aCursorRect,
+ nsIWidget* aNewOriginWidget,
+ mozilla::LayoutDeviceIntRect& aOutRect);
+
+ bool ConvertToANSIString(const nsAFlatString& aStr,
+ UINT aCodePage,
+ nsACString& aANSIStr);
+
+ bool SetIMERelatedWindowsPos(nsWindow* aWindow,
+ const IMEContext& aContext);
+ void SetIMERelatedWindowsPosOnPlugin(nsWindow* aWindow,
+ const IMEContext& aContext);
+ /**
+ * GetCharacterRectOfSelectedTextAt() returns character rect of the offset
+ * from the selection start or the start of composition string if there is
+ * a composition.
+ *
+ * @param aWindow The window which has focus.
+ * @param aOffset Offset from the selection start or the start of
+ * composition string when there is a composition.
+ * This must be in the selection range or
+ * the composition string.
+ * @param aCharRect The result.
+ * @param aWritingMode The writing mode of current selection. When this
+ * is nullptr, this assumes that the selection is in
+ * horizontal writing mode.
+ * @return true if this succeeded to retrieve the rect.
+ * Otherwise, false.
+ */
+ bool GetCharacterRectOfSelectedTextAt(
+ nsWindow* aWindow,
+ uint32_t aOffset,
+ mozilla::LayoutDeviceIntRect& aCharRect,
+ mozilla::WritingMode* aWritingMode = nullptr);
+ /**
+ * GetCaretRect() returns caret rect at current selection start.
+ *
+ * @param aWindow The window which has focus.
+ * @param aCaretRect The result.
+ * @param aWritingMode The writing mode of current selection. When this
+ * is nullptr, this assumes that the selection is in
+ * horizontal writing mode.
+ * @return true if this succeeded to retrieve the rect.
+ * Otherwise, false.
+ */
+ bool GetCaretRect(nsWindow* aWindow,
+ mozilla::LayoutDeviceIntRect& aCaretRect,
+ mozilla::WritingMode* aWritingMode = nullptr);
+ void GetCompositionString(const IMEContext& aContext,
+ DWORD aIndex,
+ nsAString& aCompositionString) const;
+
+ /**
+ * AdjustCompositionFont() makes IME vertical writing mode if it's supported.
+ * If aForceUpdate is true, it will update composition font even if writing
+ * mode isn't being changed.
+ */
+ void AdjustCompositionFont(nsWindow* aWindow,
+ const IMEContext& aContext,
+ const mozilla::WritingMode& aWritingMode,
+ bool aForceUpdate = false);
+
+ /**
+ * MaybeAdjustCompositionFont() calls AdjustCompositionFont() when the
+ * locale of active IME is CJK. Note that this creates an instance even
+ * when there is no composition but the locale is CJK.
+ */
+ static void MaybeAdjustCompositionFont(
+ nsWindow* aWindow,
+ const mozilla::WritingMode& aWritingMode,
+ bool aForceUpdate = false);
+
+ /**
+ * Get the current target clause of composition string.
+ * If there are one or more characters whose attribute is ATTR_TARGET_*,
+ * this returns the first character's offset and its length.
+ * Otherwise, e.g., the all characters are ATTR_INPUT, this returns
+ * the composition string range because the all is the current target.
+ *
+ * aLength can be null (default), but aOffset must not be null.
+ *
+ * The aOffset value is offset in the contents. So, when you need offset
+ * in the composition string, you need to subtract mCompositionStart from it.
+ */
+ bool GetTargetClauseRange(uint32_t *aOffset, uint32_t *aLength = nullptr);
+
+ /**
+ * DispatchEvent() dispatches aEvent if aWidget hasn't been destroyed yet.
+ */
+ static void DispatchEvent(nsWindow* aWindow, WidgetGUIEvent& aEvent);
+
+ /**
+ * DispatchCompositionChangeEvent() dispatches eCompositionChange event
+ * with clause information (it'll be retrieved by CreateTextRangeArray()).
+ * I.e., this should be called only during composing. If a composition is
+ * being committed, only HandleCompositionEnd() should be called.
+ *
+ * @param aWindow The window which has the composition.
+ * @param aContext Native IME context which has the composition.
+ */
+ void DispatchCompositionChangeEvent(nsWindow* aWindow,
+ const IMEContext& aContext);
+
+ nsresult EnsureClauseArray(int32_t aCount);
+ nsresult EnsureAttributeArray(int32_t aCount);
+
+ /**
+ * When WM_IME_CHAR is received and passed to DefWindowProc, we need to
+ * record the messages. In other words, we should record the messages
+ * when we receive WM_IME_CHAR on windowless plug-in (if we have focus,
+ * we always eat them). When focus is moved from a windowless plug-in to
+ * our window during composition, WM_IME_CHAR messages were received when
+ * the plug-in has focus. However, WM_CHAR messages are received after the
+ * plug-in lost focus. So, we need to ignore the WM_CHAR messages because
+ * they make unexpected text input events on us.
+ */
+ nsTArray<MSG> mPassedIMEChar;
+
+ bool IsIMECharRecordsEmpty()
+ {
+ return mPassedIMEChar.IsEmpty();
+ }
+ void ResetIMECharRecords()
+ {
+ mPassedIMEChar.Clear();
+ }
+ void DequeueIMECharRecords(WPARAM &wParam, LPARAM &lParam)
+ {
+ MSG msg = mPassedIMEChar.ElementAt(0);
+ wParam = msg.wParam;
+ lParam = msg.lParam;
+ mPassedIMEChar.RemoveElementAt(0);
+ }
+ void EnqueueIMECharRecords(WPARAM wParam, LPARAM lParam)
+ {
+ MSG msg;
+ msg.wParam = wParam;
+ msg.lParam = lParam;
+ mPassedIMEChar.AppendElement(msg);
+ }
+
+ TextEventDispatcher* GetTextEventDispatcherFor(nsWindow* aWindow);
+
+ nsWindow* mComposingWindow;
+ RefPtr<TextEventDispatcher> mDispatcher;
+ nsString mCompositionString;
+ InfallibleTArray<uint32_t> mClauseArray;
+ InfallibleTArray<uint8_t> mAttributeArray;
+
+ int32_t mCursorPosition;
+ uint32_t mCompositionStart;
+
+ struct Selection
+ {
+ nsString mString;
+ uint32_t mOffset;
+ mozilla::WritingMode mWritingMode;
+ bool mIsValid;
+
+ Selection()
+ : mOffset(UINT32_MAX)
+ , mIsValid(false)
+ {
+ }
+
+ void Clear()
+ {
+ mOffset = UINT32_MAX;
+ mIsValid = false;
+ }
+ uint32_t Length() const { return mString.Length(); }
+ bool Collapsed() const { return !Length(); }
+
+ bool IsValid() const;
+ bool Update(const IMENotification& aIMENotification);
+ bool Init(nsWindow* aWindow);
+ bool EnsureValidSelection(nsWindow* aWindow);
+ private:
+ Selection(const Selection& aOther) = delete;
+ void operator =(const Selection& aOther) = delete;
+ };
+ // mSelection stores the latest selection data only when sHasFocus is true.
+ // Don't access mSelection directly. You should use GetSelection() for
+ // getting proper state.
+ Selection mSelection;
+
+ Selection& GetSelection()
+ {
+ // When IME has focus, mSelection is automatically updated by
+ // NOTIFY_IME_OF_SELECTION_CHANGE.
+ if (sHasFocus) {
+ return mSelection;
+ }
+ // Otherwise, i.e., While IME doesn't have focus, we cannot observe
+ // selection changes. So, in such case, we need to query selection
+ // when it's necessary.
+ static Selection sTempSelection;
+ sTempSelection.Clear();
+ return sTempSelection;
+ }
+
+ bool mIsComposing;
+ bool mIsComposingOnPlugin;
+ bool mNativeCaretIsCreated;
+
+ static mozilla::WritingMode sWritingModeOfCompositionFont;
+ static nsString sIMEName;
+ static UINT sCodePage;
+ static DWORD sIMEProperty;
+ static DWORD sIMEUIProperty;
+ static bool sAssumeVerticalWritingModeNotSupported;
+ static bool sHasFocus;
+ static bool sNativeCaretIsCreatedForPlugin;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // IMMHandler_h_