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