1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #ifndef IMMHandler_h_ 7 #define IMMHandler_h_ 8 9 #include "nscore.h" 10 #include <windows.h> 11 #include "nsCOMPtr.h" 12 #include "nsString.h" 13 #include "nsTArray.h" 14 #include "nsIWidget.h" 15 #include "mozilla/EventForwards.h" 16 #include "mozilla/TextEventDispatcher.h" 17 #include "nsRect.h" 18 #include "WritingModes.h" 19 #include "npapi.h" 20 21 class nsWindow; 22 class nsWindowBase; 23 24 namespace mozilla { 25 namespace widget { 26 27 struct MSGResult; 28 29 class IMEContext final { 30 public: IMEContext()31 IMEContext() : mWnd(nullptr), mIMC(nullptr) {} 32 33 explicit IMEContext(HWND aWnd); 34 explicit IMEContext(nsWindowBase* aWindowBase); 35 ~IMEContext()36 ~IMEContext() { Clear(); } 37 get()38 HIMC get() const { return mIMC; } 39 40 void Init(HWND aWnd); 41 void Init(nsWindowBase* aWindowBase); 42 void Clear(); 43 IsValid()44 bool IsValid() const { return !!mIMC; } 45 SetOpenState(bool aOpen)46 void SetOpenState(bool aOpen) const { 47 if (!mIMC) { 48 return; 49 } 50 ::ImmSetOpenStatus(mIMC, aOpen); 51 } 52 GetOpenState()53 bool GetOpenState() const { 54 if (!mIMC) { 55 return false; 56 } 57 return (::ImmGetOpenStatus(mIMC) != FALSE); 58 } 59 AssociateDefaultContext()60 bool AssociateDefaultContext() { 61 // We assume that there is only default IMC, no new IMC has been created. 62 if (mIMC) { 63 return false; 64 } 65 if (!::ImmAssociateContextEx(mWnd, nullptr, IACE_DEFAULT)) { 66 return false; 67 } 68 mIMC = ::ImmGetContext(mWnd); 69 return (mIMC != nullptr); 70 } 71 Disassociate()72 bool Disassociate() { 73 if (!mIMC) { 74 return false; 75 } 76 if (!::ImmAssociateContextEx(mWnd, nullptr, 0)) { 77 return false; 78 } 79 ::ImmReleaseContext(mWnd, mIMC); 80 mIMC = nullptr; 81 return true; 82 } 83 84 protected: IMEContext(const IMEContext & aOther)85 IMEContext(const IMEContext& aOther) { MOZ_CRASH("Don't copy IMEContext"); } 86 87 HWND mWnd; 88 HIMC mIMC; 89 }; 90 91 class IMMHandler final { 92 public: 93 static void Initialize(); 94 static void Terminate(); 95 96 // If Process*() returns true, the caller shouldn't do anything anymore. 97 static bool ProcessMessage(nsWindow* aWindow, UINT msg, WPARAM& wParam, 98 LPARAM& lParam, MSGResult& aResult); IsComposing()99 static bool IsComposing() { return IsComposingOnOurEditor(); } IsComposingOn(nsWindow * aWindow)100 static bool IsComposingOn(nsWindow* aWindow) { 101 return IsComposing() && IsComposingWindow(aWindow); 102 } 103 104 #ifdef DEBUG 105 /** 106 * IsIMEAvailable() returns TRUE when current keyboard layout has IME. 107 * Otherwise, FALSE. 108 */ IsIMEAvailable()109 static bool IsIMEAvailable() { return !!::ImmIsIME(::GetKeyboardLayout(0)); } 110 #endif 111 112 // If aForce is TRUE, these methods doesn't check whether we have composition 113 // or not. If you don't set it to TRUE, these method doesn't commit/cancel 114 // the composition on uexpected window. 115 static void CommitComposition(nsWindow* aWindow, bool aForce = false); 116 static void CancelComposition(nsWindow* aWindow, bool aForce = false); 117 static void OnFocusChange(bool aFocus, nsWindow* aWindow); 118 static void OnUpdateComposition(nsWindow* aWindow); 119 static void OnSelectionChange(nsWindow* aWindow, 120 const IMENotification& aIMENotification, 121 bool aIsIMMActive); 122 123 static IMENotificationRequests GetIMENotificationRequests(); 124 125 // Returns NS_SUCCESS_EVENT_CONSUMED if the mouse button event is consumed by 126 // IME. Otherwise, NS_OK. 127 static nsresult OnMouseButtonEvent(nsWindow* aWindow, 128 const IMENotification& aIMENotification); 129 130 #define DECL_IS_IME_ACTIVE(aReadableName) \ 131 static bool Is##aReadableName##Active(); 132 133 // Japanese IMEs 134 DECL_IS_IME_ACTIVE(ATOK2006) 135 DECL_IS_IME_ACTIVE(ATOK2007) 136 DECL_IS_IME_ACTIVE(ATOK2008) 137 DECL_IS_IME_ACTIVE(ATOK2009) 138 DECL_IS_IME_ACTIVE(ATOK2010) 139 DECL_IS_IME_ACTIVE(GoogleJapaneseInput) 140 DECL_IS_IME_ACTIVE(Japanist2003) 141 142 #undef DECL_IS_IME_ACTIVE 143 144 /** 145 * IsActiveIMEInBlockList() returns true if we know active keyboard layout's 146 * IME has some crash bugs or something which make some damage to us. When 147 * this returns true, IMC shouldn't be associated with any windows. 148 */ 149 static bool IsActiveIMEInBlockList(); 150 151 protected: 152 static void EnsureHandlerInstance(); 153 154 static bool IsComposingOnOurEditor(); 155 static bool IsComposingWindow(nsWindow* aWindow); 156 157 static bool ShouldDrawCompositionStringOurselves(); 158 static bool IsVerticalWritingSupported(); 159 // aWindow can be nullptr if it's called without receiving WM_INPUTLANGCHANGE. 160 static void InitKeyboardLayout(nsWindow* aWindow, HKL aKeyboardLayout); 161 static UINT GetKeyboardCodePage(); 162 163 /** 164 * Checks whether the window is top level window of the composing window. 165 * In this method, the top level window means in all windows, not only in all 166 * OUR windows. I.e., if the aWindow is embedded, this always returns FALSE. 167 */ 168 static bool IsTopLevelWindowOfComposition(nsWindow* aWindow); 169 170 static bool ProcessInputLangChangeMessage(nsWindow* aWindow, WPARAM wParam, 171 LPARAM lParam, MSGResult& aResult); 172 173 IMMHandler(); 174 ~IMMHandler(); 175 176 // On*() methods return true if the caller of message handler shouldn't do 177 // anything anymore. Otherwise, false. 178 static bool OnKeyDownEvent(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, 179 MSGResult& aResult); 180 181 bool OnIMEStartComposition(nsWindow* aWindow, MSGResult& aResult); 182 bool OnIMEComposition(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, 183 MSGResult& aResult); 184 bool OnIMEEndComposition(nsWindow* aWindow, MSGResult& aResult); 185 bool OnIMERequest(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, 186 MSGResult& aResult); 187 bool OnChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, 188 MSGResult& aResult); 189 void OnInputLangChange(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, 190 MSGResult& aResult); 191 192 // These message handlers don't use instance members, we should not create 193 // the instance by the messages. So, they should be static. 194 static bool OnIMEChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, 195 MSGResult& aResult); 196 static bool OnIMESetContext(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, 197 MSGResult& aResult); 198 static bool OnIMECompositionFull(nsWindow* aWindow, MSGResult& aResult); 199 static bool OnIMENotify(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, 200 MSGResult& aResult); 201 static bool OnIMESelect(nsWindow* aWindow, WPARAM wParam, LPARAM lParam, 202 MSGResult& aResult); 203 204 // The result of Handle* method mean "Processed" when it's TRUE. 205 void HandleStartComposition(nsWindow* aWindow, const IMEContext& aContext); 206 bool HandleComposition(nsWindow* aWindow, const IMEContext& aContext, 207 LPARAM lParam); 208 // If aCommitString is null, this commits composition with the latest 209 // dispatched data. Otherwise, commits composition with the value. 210 void HandleEndComposition(nsWindow* aWindow, 211 const nsAString* aCommitString = nullptr); 212 bool HandleReconvert(nsWindow* aWindow, LPARAM lParam, LRESULT* oResult); 213 bool HandleQueryCharPosition(nsWindow* aWindow, LPARAM lParam, 214 LRESULT* oResult); 215 bool HandleDocumentFeed(nsWindow* aWindow, LPARAM lParam, LRESULT* oResult); 216 217 /** 218 * When a window's IME context is activating but we have composition on 219 * another window, we should commit our composition because IME context is 220 * shared by all our windows (including plug-ins). 221 * @param aWindow is a new activated window. 222 * If aWindow is our composing window, this method does nothing. 223 * Otherwise, this commits the composition on the previous window. 224 * If this method did commit a composition, this returns TRUE. 225 */ 226 bool CommitCompositionOnPreviousWindow(nsWindow* aWindow); 227 228 /** 229 * ResolveIMECaretPos 230 * Convert the caret rect of a composition event to another widget's 231 * coordinate system. 232 * 233 * @param aReferenceWidget The origin widget of aCursorRect. 234 * Typically, this is mReferenceWidget of the 235 * composing events. If the aCursorRect is in screen 236 * coordinates, set nullptr. 237 * @param aCursorRect The cursor rect. 238 * @param aNewOriginWidget aOutRect will be in this widget's coordinates. If 239 * this is nullptr, aOutRect will be in screen 240 * coordinates. 241 * @param aOutRect The converted cursor rect. 242 */ 243 void ResolveIMECaretPos(nsIWidget* aReferenceWidget, 244 mozilla::LayoutDeviceIntRect& aCursorRect, 245 nsIWidget* aNewOriginWidget, 246 mozilla::LayoutDeviceIntRect& aOutRect); 247 248 bool ConvertToANSIString(const nsString& aStr, UINT aCodePage, 249 nsACString& aANSIStr); 250 251 bool SetIMERelatedWindowsPos(nsWindow* aWindow, const IMEContext& aContext); 252 /** 253 * GetCharacterRectOfSelectedTextAt() returns character rect of the offset 254 * from the selection start or the start of composition string if there is 255 * a composition. 256 * 257 * @param aWindow The window which has focus. 258 * @param aOffset Offset from the selection start or the start of 259 * composition string when there is a composition. 260 * This must be in the selection range or 261 * the composition string. 262 * @param aCharRect The result. 263 * @param aWritingMode The writing mode of current selection. When this 264 * is nullptr, this assumes that the selection is in 265 * horizontal writing mode. 266 * @return true if this succeeded to retrieve the rect. 267 * Otherwise, false. 268 */ 269 bool GetCharacterRectOfSelectedTextAt( 270 nsWindow* aWindow, uint32_t aOffset, 271 mozilla::LayoutDeviceIntRect& aCharRect, 272 mozilla::WritingMode* aWritingMode = nullptr); 273 /** 274 * GetCaretRect() returns caret rect at current selection start. 275 * 276 * @param aWindow The window which has focus. 277 * @param aCaretRect The result. 278 * @param aWritingMode The writing mode of current selection. When this 279 * is nullptr, this assumes that the selection is in 280 * horizontal writing mode. 281 * @return true if this succeeded to retrieve the rect. 282 * Otherwise, false. 283 */ 284 bool GetCaretRect(nsWindow* aWindow, mozilla::LayoutDeviceIntRect& aCaretRect, 285 mozilla::WritingMode* aWritingMode = nullptr); 286 void GetCompositionString(const IMEContext& aContext, DWORD aIndex, 287 nsAString& aCompositionString) const; 288 289 /** 290 * AdjustCompositionFont() makes IME vertical writing mode if it's supported. 291 * If aForceUpdate is true, it will update composition font even if writing 292 * mode isn't being changed. 293 */ 294 void AdjustCompositionFont(nsWindow* aWindow, const IMEContext& aContext, 295 const mozilla::WritingMode& aWritingMode, 296 bool aForceUpdate = false); 297 298 /** 299 * MaybeAdjustCompositionFont() calls AdjustCompositionFont() when the 300 * locale of active IME is CJK. Note that this creates an instance even 301 * when there is no composition but the locale is CJK. 302 */ 303 static void MaybeAdjustCompositionFont( 304 nsWindow* aWindow, const mozilla::WritingMode& aWritingMode, 305 bool aForceUpdate = false); 306 307 /** 308 * Get the current target clause of composition string. 309 * If there are one or more characters whose attribute is ATTR_TARGET_*, 310 * this returns the first character's offset and its length. 311 * Otherwise, e.g., the all characters are ATTR_INPUT, this returns 312 * the composition string range because the all is the current target. 313 * 314 * aLength can be null (default), but aOffset must not be null. 315 * 316 * The aOffset value is offset in the contents. So, when you need offset 317 * in the composition string, you need to subtract mCompositionStart from it. 318 */ 319 bool GetTargetClauseRange(uint32_t* aOffset, uint32_t* aLength = nullptr); 320 321 /** 322 * DispatchEvent() dispatches aEvent if aWidget hasn't been destroyed yet. 323 */ 324 static void DispatchEvent(nsWindow* aWindow, WidgetGUIEvent& aEvent); 325 326 /** 327 * DispatchCompositionChangeEvent() dispatches eCompositionChange event 328 * with clause information (it'll be retrieved by CreateTextRangeArray()). 329 * I.e., this should be called only during composing. If a composition is 330 * being committed, only HandleCompositionEnd() should be called. 331 * 332 * @param aWindow The window which has the composition. 333 * @param aContext Native IME context which has the composition. 334 */ 335 void DispatchCompositionChangeEvent(nsWindow* aWindow, 336 const IMEContext& aContext); 337 338 nsresult EnsureClauseArray(int32_t aCount); 339 nsresult EnsureAttributeArray(int32_t aCount); 340 341 /** 342 * When WM_IME_CHAR is received and passed to DefWindowProc, we need to 343 * record the messages. In other words, we should record the messages 344 * when we receive WM_IME_CHAR on windowless plug-in (if we have focus, 345 * we always eat them). When focus is moved from a windowless plug-in to 346 * our window during composition, WM_IME_CHAR messages were received when 347 * the plug-in has focus. However, WM_CHAR messages are received after the 348 * plug-in lost focus. So, we need to ignore the WM_CHAR messages because 349 * they make unexpected text input events on us. 350 */ 351 nsTArray<MSG> mPassedIMEChar; 352 IsIMECharRecordsEmpty()353 bool IsIMECharRecordsEmpty() { return mPassedIMEChar.IsEmpty(); } ResetIMECharRecords()354 void ResetIMECharRecords() { mPassedIMEChar.Clear(); } DequeueIMECharRecords(WPARAM & wParam,LPARAM & lParam)355 void DequeueIMECharRecords(WPARAM& wParam, LPARAM& lParam) { 356 MSG msg = mPassedIMEChar.ElementAt(0); 357 wParam = msg.wParam; 358 lParam = msg.lParam; 359 mPassedIMEChar.RemoveElementAt(0); 360 } EnqueueIMECharRecords(WPARAM wParam,LPARAM lParam)361 void EnqueueIMECharRecords(WPARAM wParam, LPARAM lParam) { 362 MSG msg; 363 msg.wParam = wParam; 364 msg.lParam = lParam; 365 mPassedIMEChar.AppendElement(msg); 366 } 367 368 TextEventDispatcher* GetTextEventDispatcherFor(nsWindow* aWindow); 369 370 nsWindow* mComposingWindow; 371 RefPtr<TextEventDispatcher> mDispatcher; 372 nsString mCompositionString; 373 nsTArray<uint32_t> mClauseArray; 374 nsTArray<uint8_t> mAttributeArray; 375 376 int32_t mCursorPosition; 377 uint32_t mCompositionStart; 378 379 struct Selection { 380 nsString mString; 381 uint32_t mOffset; 382 mozilla::WritingMode mWritingMode; 383 bool mIsValid; 384 SelectionSelection385 Selection() : mOffset(UINT32_MAX), mIsValid(false) {} 386 ClearSelection387 void Clear() { 388 mOffset = UINT32_MAX; 389 mIsValid = false; 390 } LengthSelection391 uint32_t Length() const { return mString.Length(); } CollapsedSelection392 bool Collapsed() const { return !Length(); } 393 394 bool IsValid() const; 395 bool Update(const IMENotification& aIMENotification); 396 bool Init(nsWindow* aWindow); 397 bool EnsureValidSelection(nsWindow* aWindow); 398 399 private: 400 Selection(const Selection& aOther) = delete; 401 void operator=(const Selection& aOther) = delete; 402 }; 403 // mSelection stores the latest selection data only when sHasFocus is true. 404 // Don't access mSelection directly. You should use GetSelection() for 405 // getting proper state. 406 Selection mSelection; 407 GetSelection()408 Selection& GetSelection() { 409 // When IME has focus, mSelection is automatically updated by 410 // NOTIFY_IME_OF_SELECTION_CHANGE. 411 if (sHasFocus) { 412 return mSelection; 413 } 414 // Otherwise, i.e., While IME doesn't have focus, we cannot observe 415 // selection changes. So, in such case, we need to query selection 416 // when it's necessary. 417 static Selection sTempSelection; 418 sTempSelection.Clear(); 419 return sTempSelection; 420 } 421 422 bool mIsComposing; 423 424 static mozilla::WritingMode sWritingModeOfCompositionFont; 425 static nsString sIMEName; 426 static UINT sCodePage; 427 static DWORD sIMEProperty; 428 static DWORD sIMEUIProperty; 429 static bool sAssumeVerticalWritingModeNotSupported; 430 static bool sHasFocus; 431 }; 432 433 } // namespace widget 434 } // namespace mozilla 435 436 #endif // IMMHandler_h_ 437