Merge mozilla-beta to b2g34. a=merge
[gecko.git] / widget / windows / KeyboardLayout.h
blobe9a39197b5fa33d0c7a6064cc832e48e5842233a
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/. */
6 #ifndef KeyboardLayout_h__
7 #define KeyboardLayout_h__
9 #include "nscore.h"
10 #include "nsAutoPtr.h"
11 #include "nsString.h"
12 #include "nsWindowBase.h"
13 #include "nsWindowDefs.h"
14 #include "mozilla/Attributes.h"
15 #include "mozilla/EventForwards.h"
16 #include <windows.h>
18 #define NS_NUM_OF_KEYS 70
20 #define VK_OEM_1 0xBA // ';:' for US
21 #define VK_OEM_PLUS 0xBB // '+' any country
22 #define VK_OEM_COMMA 0xBC
23 #define VK_OEM_MINUS 0xBD // '-' any country
24 #define VK_OEM_PERIOD 0xBE
25 #define VK_OEM_2 0xBF
26 #define VK_OEM_3 0xC0
27 // '/?' for Brazilian (ABNT)
28 #define VK_ABNT_C1 0xC1
29 // Separator in Numpad for Brazilian (ABNT) or JIS keyboard for Mac.
30 #define VK_ABNT_C2 0xC2
31 #define VK_OEM_4 0xDB
32 #define VK_OEM_5 0xDC
33 #define VK_OEM_6 0xDD
34 #define VK_OEM_7 0xDE
35 #define VK_OEM_8 0xDF
36 #define VK_OEM_102 0xE2
37 #define VK_OEM_CLEAR 0xFE
39 class nsIIdleServiceInternal;
40 struct nsModifierKeyState;
42 namespace mozilla {
43 namespace widget {
45 static const uint32_t sModifierKeyMap[][3] = {
46 { nsIWidget::CAPS_LOCK, VK_CAPITAL, 0 },
47 { nsIWidget::NUM_LOCK, VK_NUMLOCK, 0 },
48 { nsIWidget::SHIFT_L, VK_SHIFT, VK_LSHIFT },
49 { nsIWidget::SHIFT_R, VK_SHIFT, VK_RSHIFT },
50 { nsIWidget::CTRL_L, VK_CONTROL, VK_LCONTROL },
51 { nsIWidget::CTRL_R, VK_CONTROL, VK_RCONTROL },
52 { nsIWidget::ALT_L, VK_MENU, VK_LMENU },
53 { nsIWidget::ALT_R, VK_MENU, VK_RMENU }
56 class KeyboardLayout;
58 class ModifierKeyState
60 public:
61 ModifierKeyState();
62 ModifierKeyState(bool aIsShiftDown, bool aIsControlDown, bool aIsAltDown);
63 ModifierKeyState(Modifiers aModifiers);
65 MOZ_ALWAYS_INLINE void Update();
67 MOZ_ALWAYS_INLINE void Unset(Modifiers aRemovingModifiers);
68 void Set(Modifiers aAddingModifiers);
70 void InitInputEvent(WidgetInputEvent& aInputEvent) const;
72 bool IsShift() const;
73 bool IsControl() const;
74 MOZ_ALWAYS_INLINE bool IsAlt() const;
75 MOZ_ALWAYS_INLINE bool IsAltGr() const;
76 MOZ_ALWAYS_INLINE bool IsWin() const;
78 MOZ_ALWAYS_INLINE bool IsCapsLocked() const;
79 MOZ_ALWAYS_INLINE bool IsNumLocked() const;
80 MOZ_ALWAYS_INLINE bool IsScrollLocked() const;
82 MOZ_ALWAYS_INLINE Modifiers GetModifiers() const;
84 private:
85 Modifiers mModifiers;
87 MOZ_ALWAYS_INLINE void EnsureAltGr();
89 void InitMouseEvent(WidgetInputEvent& aMouseEvent) const;
92 struct UniCharsAndModifiers
94 // Dead-key + up to 4 characters
95 char16_t mChars[5];
96 Modifiers mModifiers[5];
97 uint32_t mLength;
99 UniCharsAndModifiers() : mLength(0) {}
100 UniCharsAndModifiers operator+(const UniCharsAndModifiers& aOther) const;
101 UniCharsAndModifiers& operator+=(const UniCharsAndModifiers& aOther);
104 * Append a pair of unicode character and the final modifier.
106 void Append(char16_t aUniChar, Modifiers aModifiers);
107 void Clear() { mLength = 0; }
108 bool IsEmpty() const { return !mLength; }
110 void FillModifiers(Modifiers aModifiers);
112 bool UniCharsEqual(const UniCharsAndModifiers& aOther) const;
113 bool UniCharsCaseInsensitiveEqual(const UniCharsAndModifiers& aOther) const;
115 nsString ToString() const { return nsString(mChars, mLength); }
118 struct DeadKeyEntry;
119 class DeadKeyTable;
122 class VirtualKey
124 public:
125 // 0 - Normal
126 // 1 - Shift
127 // 2 - Control
128 // 3 - Control + Shift
129 // 4 - Alt
130 // 5 - Alt + Shift
131 // 6 - Alt + Control (AltGr)
132 // 7 - Alt + Control + Shift (AltGr + Shift)
133 // 8 - CapsLock
134 // 9 - CapsLock + Shift
135 // 10 - CapsLock + Control
136 // 11 - CapsLock + Control + Shift
137 // 12 - CapsLock + Alt
138 // 13 - CapsLock + Alt + Shift
139 // 14 - CapsLock + Alt + Control (CapsLock + AltGr)
140 // 15 - CapsLock + Alt + Control + Shift (CapsLock + AltGr + Shift)
142 enum ShiftStateFlag
144 STATE_SHIFT = 0x01,
145 STATE_CONTROL = 0x02,
146 STATE_ALT = 0x04,
147 STATE_CAPSLOCK = 0x08
150 typedef uint8_t ShiftState;
152 static ShiftState ModifiersToShiftState(Modifiers aModifiers);
153 static Modifiers ShiftStateToModifiers(ShiftState aShiftState);
155 private:
156 union KeyShiftState
158 struct
160 char16_t Chars[4];
161 } Normal;
162 struct
164 const DeadKeyTable* Table;
165 char16_t DeadChar;
166 } DeadKey;
169 KeyShiftState mShiftStates[16];
170 uint16_t mIsDeadKey;
172 void SetDeadKey(ShiftState aShiftState, bool aIsDeadKey)
174 if (aIsDeadKey) {
175 mIsDeadKey |= 1 << aShiftState;
176 } else {
177 mIsDeadKey &= ~(1 << aShiftState);
181 public:
182 static void FillKbdState(PBYTE aKbdState, const ShiftState aShiftState);
184 bool IsDeadKey(ShiftState aShiftState) const
186 return (mIsDeadKey & (1 << aShiftState)) != 0;
189 void AttachDeadKeyTable(ShiftState aShiftState,
190 const DeadKeyTable* aDeadKeyTable)
192 mShiftStates[aShiftState].DeadKey.Table = aDeadKeyTable;
195 void SetNormalChars(ShiftState aShiftState, const char16_t* aChars,
196 uint32_t aNumOfChars);
197 void SetDeadChar(ShiftState aShiftState, char16_t aDeadChar);
198 const DeadKeyTable* MatchingDeadKeyTable(const DeadKeyEntry* aDeadKeyArray,
199 uint32_t aEntries) const;
200 inline char16_t GetCompositeChar(ShiftState aShiftState,
201 char16_t aBaseChar) const;
202 UniCharsAndModifiers GetNativeUniChars(ShiftState aShiftState) const;
203 UniCharsAndModifiers GetUniChars(ShiftState aShiftState) const;
206 class MOZ_STACK_CLASS NativeKey
208 friend class KeyboardLayout;
210 public:
211 struct FakeCharMsg
213 UINT mCharCode;
214 UINT mScanCode;
215 bool mIsDeadKey;
216 bool mConsumed;
218 FakeCharMsg() :
219 mCharCode(0), mScanCode(0), mIsDeadKey(false), mConsumed(false)
223 MSG GetCharMsg(HWND aWnd) const
225 MSG msg;
226 msg.hwnd = aWnd;
227 msg.message = mIsDeadKey ? WM_DEADCHAR : WM_CHAR;
228 msg.wParam = static_cast<WPARAM>(mCharCode);
229 msg.lParam = static_cast<LPARAM>(mScanCode << 16);
230 msg.time = 0;
231 msg.pt.x = msg.pt.y = 0;
232 return msg;
236 NativeKey(nsWindowBase* aWidget,
237 const MSG& aKeyOrCharMessage,
238 const ModifierKeyState& aModKeyState,
239 nsTArray<FakeCharMsg>* aFakeCharMsgs = nullptr);
242 * Handle WM_KEYDOWN message or WM_SYSKEYDOWN message. The instance must be
243 * initialized with WM_KEYDOWN or WM_SYSKEYDOWN.
244 * Returns true if dispatched keydown event or keypress event is consumed.
245 * Otherwise, false.
247 bool HandleKeyDownMessage(bool* aEventDispatched = nullptr) const;
250 * Handles WM_CHAR message or WM_SYSCHAR message. The instance must be
251 * initialized with WM_KEYDOWN, WM_SYSKEYDOWN or them.
252 * Returns true if dispatched keypress event is consumed. Otherwise, false.
254 bool HandleCharMessage(const MSG& aCharMsg,
255 bool* aEventDispatched = nullptr) const;
258 * Handles keyup message. Returns true if the event is consumed.
259 * Otherwise, false.
261 bool HandleKeyUpMessage(bool* aEventDispatched = nullptr) const;
263 private:
264 nsRefPtr<nsWindowBase> mWidget;
265 HKL mKeyboardLayout;
266 MSG mMsg;
268 uint32_t mDOMKeyCode;
269 KeyNameIndex mKeyNameIndex;
270 CodeNameIndex mCodeNameIndex;
272 ModifierKeyState mModKeyState;
274 // mVirtualKeyCode distinguishes left key or right key of modifier key.
275 uint8_t mVirtualKeyCode;
276 // mOriginalVirtualKeyCode doesn't distinguish left key or right key of
277 // modifier key. However, if the given keycode is VK_PROCESS, it's resolved
278 // to a keycode before it's handled by IME.
279 uint8_t mOriginalVirtualKeyCode;
281 // mCommittedChars indicates the inputted characters which is committed by
282 // the key. If dead key fail to composite a character, mCommittedChars
283 // indicates both the dead characters and the base characters.
284 UniCharsAndModifiers mCommittedCharsAndModifiers;
286 WORD mScanCode;
287 bool mIsExtended;
288 bool mIsDeadKey;
289 // mIsPrintableKey is true if the key may be a printable key without
290 // any modifier keys. Otherwise, false.
291 // Please note that the event may not cause any text input even if this
292 // is true. E.g., it might be dead key state or Ctrl key may be pressed.
293 bool mIsPrintableKey;
295 nsTArray<FakeCharMsg>* mFakeCharMsgs;
297 NativeKey()
299 MOZ_CRASH("The default constructor of NativeKey isn't available");
303 * Returns true if the key event is caused by auto repeat.
305 bool IsRepeat() const
307 switch (mMsg.message) {
308 case WM_KEYDOWN:
309 case WM_SYSKEYDOWN:
310 case WM_CHAR:
311 case WM_SYSCHAR:
312 case WM_DEADCHAR:
313 case WM_SYSDEADCHAR:
314 return ((mMsg.lParam & (1 << 30)) != 0);
315 default:
316 return false;
320 UINT GetScanCodeWithExtendedFlag() const;
322 // The result is one of nsIDOMKeyEvent::DOM_KEY_LOCATION_*.
323 uint32_t GetKeyLocation() const;
326 * "Kakutei-Undo" of ATOK or WXG (both of them are Japanese IME) causes
327 * strange WM_KEYDOWN/WM_KEYUP/WM_CHAR message pattern. So, when this
328 * returns true, the caller needs to be careful for processing the messages.
330 bool IsIMEDoingKakuteiUndo() const;
332 bool IsKeyDownMessage() const
334 return (mMsg.message == WM_KEYDOWN || mMsg.message == WM_SYSKEYDOWN);
336 bool IsKeyUpMessage() const
338 return (mMsg.message == WM_KEYUP || mMsg.message == WM_SYSKEYUP);
340 bool IsPrintableCharMessage(const MSG& aMSG) const
342 return IsPrintableCharMessage(aMSG.message);
344 bool IsPrintableCharMessage(UINT aMessage) const
346 return (aMessage == WM_CHAR || aMessage == WM_SYSCHAR);
348 bool IsCharMessage(const MSG& aMSG) const
350 return IsCharMessage(aMSG.message);
352 bool IsCharMessage(UINT aMessage) const
354 return (IsPrintableCharMessage(aMessage) || IsDeadCharMessage(aMessage));
356 bool IsDeadCharMessage(const MSG& aMSG) const
358 return IsDeadCharMessage(aMSG.message);
360 bool IsDeadCharMessage(UINT aMessage) const
362 return (aMessage == WM_DEADCHAR || aMessage == WM_SYSDEADCHAR);
364 bool IsSysCharMessage(const MSG& aMSG) const
366 return IsSysCharMessage(aMSG.message);
368 bool IsSysCharMessage(UINT aMessage) const
370 return (aMessage == WM_SYSCHAR || aMessage == WM_SYSDEADCHAR);
372 bool MayBeSameCharMessage(const MSG& aCharMsg1, const MSG& aCharMsg2) const;
373 bool IsFollowedByDeadCharMessage() const;
376 * GetFollowingCharMessage() returns following char message of handling
377 * keydown event. If the message is found, this method returns true.
378 * Otherwise, returns false.
380 * WARNING: Even if this returns true, aCharMsg may be WM_NULL or its
381 * hwnd may be different window.
383 bool GetFollowingCharMessage(MSG& aCharMsg) const;
386 * Whether the key event can compute virtual keycode from the scancode value.
388 bool CanComputeVirtualKeyCodeFromScanCode() const;
391 * Wraps MapVirtualKeyEx() with MAPVK_VSC_TO_VK.
393 uint8_t ComputeVirtualKeyCodeFromScanCode() const;
396 * Wraps MapVirtualKeyEx() with MAPVK_VSC_TO_VK_EX.
398 uint8_t ComputeVirtualKeyCodeFromScanCodeEx() const;
401 * Wraps MapVirtualKeyEx() with MAPVK_VK_TO_VSC_EX or MAPVK_VK_TO_VSC.
403 uint16_t ComputeScanCodeExFromVirtualKeyCode(UINT aVirtualKeyCode) const;
406 * Wraps MapVirtualKeyEx() with MAPVK_VSC_TO_VK and MAPVK_VK_TO_CHAR.
408 char16_t ComputeUnicharFromScanCode() const;
411 * Initializes the aKeyEvent with the information stored in the instance.
413 void InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
414 const ModifierKeyState& aModKeyState) const;
415 void InitKeyEvent(WidgetKeyboardEvent& aKeyEvent) const;
418 * Dispatches the key event. Returns true if the event is consumed.
419 * Otherwise, false.
421 bool DispatchKeyEvent(WidgetKeyboardEvent& aKeyEvent,
422 const MSG* aMsgSentToPlugin = nullptr) const;
425 * DispatchKeyPressEventsWithKeyboardLayout() dispatches keypress event(s)
426 * with the information provided by KeyboardLayout class.
428 bool DispatchKeyPressEventsWithKeyboardLayout() const;
431 * Remove all following WM_CHAR, WM_SYSCHAR and WM_DEADCHAR messages for the
432 * WM_KEYDOWN or WM_SYSKEYDOWN message. Additionally, dispatches plugin
433 * events if it's necessary.
434 * Returns true if the widget is destroyed. Otherwise, false.
436 bool DispatchPluginEventsAndDiscardsCharMessages() const;
439 * DispatchKeyPressEventForFollowingCharMessage() dispatches keypress event
440 * for following WM_*CHAR message which is removed and set to aCharMsg.
441 * Returns true if the event is consumed. Otherwise, false.
443 bool DispatchKeyPressEventForFollowingCharMessage(const MSG& aCharMsg) const;
446 * Checkes whether the key event down message is handled without following
447 * WM_CHAR messages. For example, if following WM_CHAR message indicates
448 * control character input, the WM_CHAR message is unclear whether it's
449 * caused by a printable key with Ctrl or just a function key such as Enter
450 * or Backspace.
452 bool NeedsToHandleWithoutFollowingCharMessages() const;
455 class KeyboardLayout
457 friend class NativeKey;
459 private:
460 KeyboardLayout();
461 ~KeyboardLayout();
463 static KeyboardLayout* sInstance;
464 static nsIIdleServiceInternal* sIdleService;
466 struct DeadKeyTableListEntry
468 DeadKeyTableListEntry* next;
469 uint8_t data[1];
472 HKL mKeyboardLayout;
474 VirtualKey mVirtualKeys[NS_NUM_OF_KEYS];
475 DeadKeyTableListEntry* mDeadKeyTableListHead;
476 int32_t mActiveDeadKey; // -1 = no active dead-key
477 VirtualKey::ShiftState mDeadKeyShiftState;
479 bool mIsOverridden : 1;
480 bool mIsPendingToRestoreKeyboardLayout : 1;
482 static inline int32_t GetKeyIndex(uint8_t aVirtualKey);
483 static int CompareDeadKeyEntries(const void* aArg1, const void* aArg2,
484 void* aData);
485 static bool AddDeadKeyEntry(char16_t aBaseChar, char16_t aCompositeChar,
486 DeadKeyEntry* aDeadKeyArray, uint32_t aEntries);
487 bool EnsureDeadKeyActive(bool aIsActive, uint8_t aDeadKey,
488 const PBYTE aDeadKeyKbdState);
489 uint32_t GetDeadKeyCombinations(uint8_t aDeadKey,
490 const PBYTE aDeadKeyKbdState,
491 uint16_t aShiftStatesWithBaseChars,
492 DeadKeyEntry* aDeadKeyArray,
493 uint32_t aMaxEntries);
494 void DeactivateDeadKeyState();
495 const DeadKeyTable* AddDeadKeyTable(const DeadKeyEntry* aDeadKeyArray,
496 uint32_t aEntries);
497 void ReleaseDeadKeyTables();
500 * Loads the specified keyboard layout. This method always clear the dead key
501 * state.
503 void LoadLayout(HKL aLayout);
506 * InitNativeKey() must be called when actually widget receives WM_KEYDOWN or
507 * WM_KEYUP. This method is stateful. This saves current dead key state at
508 * WM_KEYDOWN. Additionally, computes current inputted character(s) and set
509 * them to the aNativeKey.
511 void InitNativeKey(NativeKey& aNativeKey,
512 const ModifierKeyState& aModKeyState);
514 public:
515 static KeyboardLayout* GetInstance();
516 static void Shutdown();
517 static void NotifyIdleServiceOfUserActivity();
519 static bool IsPrintableCharKey(uint8_t aVirtualKey);
522 * IsDeadKey() returns true if aVirtualKey is a dead key with aModKeyState.
523 * This method isn't stateful.
525 bool IsDeadKey(uint8_t aVirtualKey,
526 const ModifierKeyState& aModKeyState) const;
529 * GetUniCharsAndModifiers() returns characters which is inputted by the
530 * aVirtualKey with aModKeyState. This method isn't stateful.
532 UniCharsAndModifiers GetUniCharsAndModifiers(
533 uint8_t aVirtualKey,
534 const ModifierKeyState& aModKeyState) const;
537 * OnLayoutChange() must be called before the first keydown message is
538 * received. LoadLayout() changes the keyboard state, that causes breaking
539 * dead key state. Therefore, we need to load the layout before the first
540 * keydown message.
542 void OnLayoutChange(HKL aKeyboardLayout)
544 MOZ_ASSERT(!mIsOverridden);
545 LoadLayout(aKeyboardLayout);
549 * OverrideLayout() loads the specified keyboard layout.
551 void OverrideLayout(HKL aLayout)
553 mIsOverridden = true;
554 LoadLayout(aLayout);
558 * RestoreLayout() loads the current keyboard layout of the thread.
560 void RestoreLayout()
562 mIsOverridden = false;
563 mIsPendingToRestoreKeyboardLayout = true;
566 uint32_t ConvertNativeKeyCodeToDOMKeyCode(UINT aNativeKeyCode) const;
569 * ConvertNativeKeyCodeToKeyNameIndex() returns KeyNameIndex value for
570 * non-printable keys (except some special keys like space key).
572 KeyNameIndex ConvertNativeKeyCodeToKeyNameIndex(uint8_t aVirtualKey) const;
575 * ConvertScanCodeToCodeNameIndex() returns CodeNameIndex value for
576 * the given scan code. aScanCode can be over 0xE000 since this method
577 * doesn't use Windows API.
579 static CodeNameIndex ConvertScanCodeToCodeNameIndex(UINT aScanCode);
581 HKL GetLayout() const
583 return mIsPendingToRestoreKeyboardLayout ? ::GetKeyboardLayout(0) :
584 mKeyboardLayout;
588 * This wraps MapVirtualKeyEx() API with MAPVK_VK_TO_VSC.
590 WORD ComputeScanCodeForVirtualKeyCode(uint8_t aVirtualKeyCode) const;
593 * Implementation of nsIWidget::SynthesizeNativeKeyEvent().
595 nsresult SynthesizeNativeKeyEvent(nsWindowBase* aWidget,
596 int32_t aNativeKeyboardLayout,
597 int32_t aNativeKeyCode,
598 uint32_t aModifierFlags,
599 const nsAString& aCharacters,
600 const nsAString& aUnmodifiedCharacters);
603 class RedirectedKeyDownMessageManager
605 public:
607 * If a window receives WM_KEYDOWN message or WM_SYSKEYDOWM message which is
608 * a redirected message, NativeKey::DispatchKeyDownAndKeyPressEvent()
609 * prevents to dispatch NS_KEY_DOWN event because it has been dispatched
610 * before the message was redirected. However, in some cases, WM_*KEYDOWN
611 * message handler may not handle actually. Then, the message handler needs
612 * to forget the redirected message and remove WM_CHAR message or WM_SYSCHAR
613 * message for the redirected keydown message. AutoFlusher class is a helper
614 * class for doing it. This must be created in the stack.
616 class MOZ_STACK_CLASS AutoFlusher MOZ_FINAL
618 public:
619 AutoFlusher(nsWindowBase* aWidget, const MSG &aMsg) :
620 mCancel(!RedirectedKeyDownMessageManager::IsRedirectedMessage(aMsg)),
621 mWidget(aWidget), mMsg(aMsg)
625 ~AutoFlusher()
627 if (mCancel) {
628 return;
630 // Prevent unnecessary keypress event
631 if (!mWidget->Destroyed()) {
632 RedirectedKeyDownMessageManager::RemoveNextCharMessage(mMsg.hwnd);
634 // Foreget the redirected message
635 RedirectedKeyDownMessageManager::Forget();
638 void Cancel() { mCancel = true; }
640 private:
641 bool mCancel;
642 nsRefPtr<nsWindowBase> mWidget;
643 const MSG &mMsg;
646 static void WillRedirect(const MSG& aMsg, bool aDefualtPrevented)
648 sRedirectedKeyDownMsg = aMsg;
649 sDefaultPreventedOfRedirectedMsg = aDefualtPrevented;
652 static void Forget()
654 sRedirectedKeyDownMsg.message = WM_NULL;
657 static void PreventDefault() { sDefaultPreventedOfRedirectedMsg = true; }
658 static bool DefaultPrevented() { return sDefaultPreventedOfRedirectedMsg; }
660 static bool IsRedirectedMessage(const MSG& aMsg);
663 * RemoveNextCharMessage() should be called by WM_KEYDOWN or WM_SYSKEYDOWM
664 * message handler. If there is no WM_(SYS)CHAR message for it, this
665 * method does nothing.
666 * NOTE: WM_(SYS)CHAR message is posted by TranslateMessage() API which is
667 * called in message loop. So, WM_(SYS)KEYDOWN message should have
668 * WM_(SYS)CHAR message in the queue if the keydown event causes character
669 * input.
671 static void RemoveNextCharMessage(HWND aWnd);
673 private:
674 // sRedirectedKeyDownMsg is WM_KEYDOWN message or WM_SYSKEYDOWN message which
675 // is reirected with SendInput() API by
676 // widget::NativeKey::DispatchKeyDownAndKeyPressEvent()
677 static MSG sRedirectedKeyDownMsg;
678 static bool sDefaultPreventedOfRedirectedMsg;
681 } // namespace widget
682 } // namespace mozilla
684 #endif