// Copyright © 2015 The CefSharp Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

#include "Stdafx.h"
#include "CefBrowserHostWrapper.h"

#include "include\cef_client.h"
#include "include\cef_parser.h"

#include "Cef.h"
#include "CefTaskScheduler.h"
#include "DragData.h"
#include "CefRunFileDialogCallbackAdapter.h"
#include "CefPdfPrintCallbackWrapper.h"
#include "CefNavigationEntryVisitorAdapter.h"
#include "CefRegistrationWrapper.h"
#include "CefDevToolsMessageObserverAdapter.h"
#include "RequestContext.h"
#include "WindowInfo.h"

using namespace CefSharp::Core;

void CefBrowserHostWrapper::DragTargetDragEnter(IDragData^ dragData, MouseEvent mouseEvent, DragOperationsMask allowedOperations)
{
    ThrowIfDisposed();

    auto dragDataWrapper = static_cast<CefSharp::Core::DragData^>(dragData);
    dragDataWrapper->ResetFileContents(); // Recommended by documentation to reset before calling DragEnter
    _browserHost->DragTargetDragEnter(static_cast<CefRefPtr<CefDragData>>(dragDataWrapper), GetCefMouseEvent(mouseEvent), (CefBrowserHost::DragOperationsMask) allowedOperations);
}

void CefBrowserHostWrapper::DragTargetDragOver(MouseEvent mouseEvent, DragOperationsMask allowedOperations)
{
    ThrowIfDisposed();

    _browserHost->DragTargetDragOver(GetCefMouseEvent(mouseEvent), (CefBrowserHost::DragOperationsMask) allowedOperations);
}

void CefBrowserHostWrapper::DragTargetDragDrop(MouseEvent mouseEvent)
{
    ThrowIfDisposed();

    _browserHost->DragTargetDrop(GetCefMouseEvent(mouseEvent));
}

void CefBrowserHostWrapper::DragSourceEndedAt(int x, int y, DragOperationsMask op)
{
    ThrowIfDisposed();

    _browserHost->DragSourceEndedAt(x, y, (CefBrowserHost::DragOperationsMask)op);
}

void CefBrowserHostWrapper::DragTargetDragLeave()
{
    ThrowIfDisposed();

    _browserHost->DragTargetDragLeave();
}

void CefBrowserHostWrapper::DragSourceSystemDragEnded()
{
    ThrowIfDisposed();

    _browserHost->DragSourceSystemDragEnded();
}

void CefBrowserHostWrapper::StartDownload(String^ url)
{
    ThrowIfDisposed();

    _browserHost->StartDownload(StringUtils::ToNative(url));
}

void CefBrowserHostWrapper::Print()
{
    ThrowIfDisposed();

    _browserHost->Print();
}

void CefBrowserHostWrapper::PrintToPdf(String^ path, PdfPrintSettings^ settings, IPrintToPdfCallback^ callback)
{
    ThrowIfDisposed();

    CefPdfPrintSettings nativeSettings;
    if (settings != nullptr)
    {
        nativeSettings.landscape = settings->Landscape ? 1 : 0;
        nativeSettings.print_background = settings->PrintBackground ? 1 : 0;
        nativeSettings.scale = settings->Scale;
        nativeSettings.paper_height = settings->PaperHeight;
        nativeSettings.paper_width = settings->PaperWidth;
        nativeSettings.prefer_css_page_size = settings->PreferCssPageSize ? 1 : 0;
        nativeSettings.margin_type = static_cast<cef_pdf_print_margin_type_t>(settings->MarginType);
        nativeSettings.margin_bottom = settings->MarginBottom;
        nativeSettings.margin_top = settings->MarginTop;
        nativeSettings.margin_left = settings->MarginLeft;
        nativeSettings.margin_right = settings->MarginRight;
        StringUtils::AssignNativeFromClr(nativeSettings.page_ranges, settings->PageRanges);
        nativeSettings.display_header_footer = settings->DisplayHeaderFooter ? 1 : 0;
        StringUtils::AssignNativeFromClr(nativeSettings.header_template, settings->HeaderTemplate);
        StringUtils::AssignNativeFromClr(nativeSettings.footer_template, settings->FooterTemplate);
    }

    _browserHost->PrintToPDF(StringUtils::ToNative(path), nativeSettings, new CefPdfPrintCallbackWrapper(callback));
}

void CefBrowserHostWrapper::SetZoomLevel(double zoomLevel)
{
    ThrowIfDisposed();

    _browserHost->SetZoomLevel(zoomLevel);
}

double CefBrowserHostWrapper::GetZoomLevel()
{
    ThrowIfDisposed();

    if (CefCurrentlyOn(TID_UI))
    {

        return _browserHost->GetZoomLevel();
    }

    throw gcnew InvalidOperationException("This method can only be called directly on the CEF UI Thread. Use GetZoomLevelAsync or use Cef.UIThreadTaskFactory to marshal the call onto the CEF UI Thread.");

}

Task<double>^ CefBrowserHostWrapper::GetZoomLevelAsync()
{
    ThrowIfDisposed();

    if (CefCurrentlyOn(TID_UI))
    {
        return Task::FromResult(GetZoomLevelOnUI());
    }
    return Cef::UIThreadTaskFactory->StartNew(gcnew Func<double>(this, &CefBrowserHostWrapper::GetZoomLevelOnUI));
}

IntPtr CefBrowserHostWrapper::GetWindowHandle()
{
    ThrowIfDisposed();

    return IntPtr(_browserHost->GetWindowHandle());
}

void CefBrowserHostWrapper::CloseBrowser(bool forceClose)
{
    ThrowIfDisposed();

    _browserHost->CloseBrowser(forceClose);
}

bool CefBrowserHostWrapper::TryCloseBrowser()
{
    ThrowIfDisposed();
    ThrowIfExecutedOnNonCefUiThread();

    return _browserHost->TryCloseBrowser();
}

void CefBrowserHostWrapper::ShowDevTools(IWindowInfo^ windowInfo, int inspectElementAtX, int inspectElementAtY)
{
    ThrowIfDisposed();

    CefBrowserSettings settings;
    CefWindowInfo nativeWindowInfo;

    if (windowInfo == nullptr)
    {
        nativeWindowInfo.SetAsPopup(NULL, "DevTools");
    }
    else
    {
        //Get the inner instance then case that
        auto cefWindowInfoWrapper = static_cast<WindowInfo^>(windowInfo->UnWrap());

        nativeWindowInfo = *cefWindowInfoWrapper->GetWindowInfo();
    }

    _browserHost->ShowDevTools(nativeWindowInfo, _browserHost->GetClient(), settings, CefPoint(inspectElementAtX, inspectElementAtY));
}

void CefBrowserHostWrapper::CloseDevTools()
{
    ThrowIfDisposed();

    _browserHost->CloseDevTools();
}

bool CefBrowserHostWrapper::HasDevTools::get()
{
    ThrowIfDisposed();

    return _browserHost->HasDevTools();
}

bool CefBrowserHostWrapper::SendDevToolsMessage(String^ messageAsJson)
{
    ThrowIfDisposed();

    ThrowIfExecutedOnNonCefUiThread();

    if (String::IsNullOrEmpty(messageAsJson))
    {
        throw gcnew ArgumentNullException("messageAsJson");
    }

    //NOTE: Prefix with cli:: namespace as VS2015 gets confused with std::array
    cli::array<Byte>^ buffer = System::Text::Encoding::UTF8->GetBytes(messageAsJson);
    pin_ptr<Byte> src = &buffer[0];

    return _browserHost->SendDevToolsMessage(static_cast<void*>(src), buffer->Length);
}

int CefBrowserHostWrapper::ExecuteDevToolsMethod(int messageId, String^ method, IDictionary<String^, Object^>^ paramaters)
{
    ThrowIfDisposed();

    ThrowIfExecutedOnNonCefUiThread();

    if (paramaters == nullptr)
    {
        return _browserHost->ExecuteDevToolsMethod(messageId, StringUtils::ToNative(method), nullptr);
    }

    auto val = TypeConversion::ToNative(paramaters);

    if (val && val->GetType() == VTYPE_DICTIONARY)
    {
        return _browserHost->ExecuteDevToolsMethod(messageId, StringUtils::ToNative(method), val->GetDictionary());
    }

    throw gcnew Exception("Unable to convert paramaters to CefDictionaryValue.");
}



int CefBrowserHostWrapper::ExecuteDevToolsMethod(int messageId, String^ method, String^ paramsAsJson)
{
    ThrowIfDisposed();

    ThrowIfExecutedOnNonCefUiThread();

    if (String::IsNullOrEmpty(paramsAsJson))
    {
        return _browserHost->ExecuteDevToolsMethod(messageId, StringUtils::ToNative(method), nullptr);
    }

    auto val = CefParseJSON(StringUtils::ToNative(paramsAsJson), cef_json_parser_options_t::JSON_PARSER_RFC);

    if (val && val->GetType() == VTYPE_DICTIONARY)
    {
        return _browserHost->ExecuteDevToolsMethod(messageId, StringUtils::ToNative(method), val->GetDictionary());
    }

    throw gcnew Exception("Unable to parse paramsAsJson with CefParseJSON method");
}

IRegistration^ CefBrowserHostWrapper::AddDevToolsMessageObserver(IDevToolsMessageObserver^ observer)
{
    ThrowIfDisposed();

    auto registration = _browserHost->AddDevToolsMessageObserver(new CefDevToolsMessageObserverAdapter(observer));

    return gcnew CefRegistrationWrapper(registration);
}

int CefBrowserHostWrapper::GetNextDevToolsMessageId()
{
    return Interlocked::Increment(_lastDevToolsMessageId);
}

void CefBrowserHostWrapper::AddWordToDictionary(String^ word)
{
    ThrowIfDisposed();

    _browserHost->AddWordToDictionary(StringUtils::ToNative(word));
}

void CefBrowserHostWrapper::ReplaceMisspelling(String^ word)
{
    ThrowIfDisposed();

    _browserHost->ReplaceMisspelling(StringUtils::ToNative(word));
}

void CefBrowserHostWrapper::RunFileDialog(CefFileDialogMode mode, String^ title, String^ defaultFilePath, IList<String^>^ acceptFilters, IRunFileDialogCallback^ callback)
{
    ThrowIfDisposed();

    _browserHost->RunFileDialog((CefBrowserHost::FileDialogMode)mode,
        StringUtils::ToNative(title),
        StringUtils::ToNative(defaultFilePath),
        StringUtils::ToNative(acceptFilters),
        new CefRunFileDialogCallbackAdapter(callback));
}

void CefBrowserHostWrapper::Find(String^ searchText, bool forward, bool matchCase, bool findNext)
{
    ThrowIfDisposed();

    _browserHost->Find(StringUtils::ToNative(searchText), forward, matchCase, findNext);
}

void CefBrowserHostWrapper::StopFinding(bool clearSelection)
{
    ThrowIfDisposed();

    _browserHost->StopFinding(clearSelection);
}

void CefBrowserHostWrapper::SetFocus(bool focus)
{
    ThrowIfDisposed();

    _browserHost->SetFocus(focus);
}

void CefBrowserHostWrapper::SendFocusEvent(bool setFocus)
{
    ThrowIfDisposed();

    _browserHost->SetFocus(setFocus);
}

void CefBrowserHostWrapper::SendKeyEvent(KeyEvent keyEvent)
{
    ThrowIfDisposed();

    CefKeyEvent nativeKeyEvent;
    nativeKeyEvent.focus_on_editable_field = keyEvent.FocusOnEditableField == 1;
    nativeKeyEvent.is_system_key = keyEvent.IsSystemKey == 1;
    nativeKeyEvent.modifiers = (uint32_t)keyEvent.Modifiers;
    nativeKeyEvent.type = (cef_key_event_type_t)keyEvent.Type;
    nativeKeyEvent.native_key_code = keyEvent.NativeKeyCode;
    nativeKeyEvent.windows_key_code = keyEvent.WindowsKeyCode;

    _browserHost->SendKeyEvent(nativeKeyEvent);
}

void CefBrowserHostWrapper::SendKeyEvent(int message, int wParam, int lParam)
{
    ThrowIfDisposed();

    CefKeyEvent keyEvent;
    keyEvent.windows_key_code = wParam;
    keyEvent.native_key_code = lParam;
    keyEvent.is_system_key = message == WM_SYSCHAR ||
        message == WM_SYSKEYDOWN ||
        message == WM_SYSKEYUP;
    keyEvent.modifiers = GetCefKeyboardModifiers(wParam, lParam);

    if (message == WM_KEYDOWN || message == WM_SYSKEYDOWN)
    {
        keyEvent.type = KEYEVENT_RAWKEYDOWN;
    }
    else if (message == WM_KEYUP || message == WM_SYSKEYUP)
    {
        keyEvent.type = KEYEVENT_KEYUP;
    }
    else
    {
        keyEvent.type = KEYEVENT_CHAR;

        // mimic alt-gr check behaviour from
        // src/ui/events/win/events_win_utils.cc: GetModifiersFromKeyState
        if (IsKeyDown(VK_MENU))
        {
            // reverse AltGr detection taken from PlatformKeyMap::UsesAltGraph
            // instead of checking all combination for ctrl-alt, just check current char
            HKL current_layout = ::GetKeyboardLayout(0);

            // https://docs.microsoft.com/en-gb/windows/win32/api/winuser/nf-winuser-vkkeyscanexw
            // ... high-order byte contains the shift state,
            // which can be a combination of the following flag bits.
            // 1 Either SHIFT key is pressed.
            // 2 Either CTRL key is pressed.
            // 4 Either ALT key is pressed.
            SHORT scan_res = ::VkKeyScanExW(wParam, current_layout);
            constexpr auto ctrlAlt = (2 | 4);
            if (((scan_res >> 8) & ctrlAlt) == ctrlAlt) // ctrl-alt pressed
            {
                keyEvent.modifiers &= ~(EVENTFLAG_CONTROL_DOWN | EVENTFLAG_ALT_DOWN);
                keyEvent.modifiers |= EVENTFLAG_ALTGR_DOWN;
            }
        }
    }

    _browserHost->SendKeyEvent(keyEvent);
}

double CefBrowserHostWrapper::GetZoomLevelOnUI()
{
    if (_disposed)
    {
        return 0.0;
    }

    CefTaskScheduler::EnsureOn(TID_UI, "CefBrowserHostWrapper::GetZoomLevel");

    //Don't throw exception if no browser host here as it's not easy to handle
    if (_browserHost.get())
    {
        return _browserHost->GetZoomLevel();
    }

    return 0.0;
}

void CefBrowserHostWrapper::SendMouseWheelEvent(MouseEvent mouseEvent, int deltaX, int deltaY)
{
    ThrowIfDisposed();

    if (_browserHost.get())
    {
        CefMouseEvent m;
        m.x = mouseEvent.X;
        m.y = mouseEvent.Y;
        m.modifiers = (uint32_t)mouseEvent.Modifiers;

        _browserHost->SendMouseWheelEvent(m, deltaX, deltaY);
    }
}

void CefBrowserHostWrapper::SendTouchEvent(TouchEvent evt)
{
    ThrowIfDisposed();

    if (_browserHost.get())
    {
        CefTouchEvent e;
        e.id = evt.Id;
        e.modifiers = (uint32_t)evt.Modifiers;
        e.pointer_type = (cef_pointer_type_t)evt.PointerType;
        e.pressure = evt.Pressure;
        e.radius_x = evt.RadiusX;
        e.radius_y = evt.RadiusY;
        e.rotation_angle = evt.RotationAngle;
        e.type = (cef_touch_event_type_t)evt.Type;
        e.x = evt.X;
        e.y = evt.Y;

        _browserHost->SendTouchEvent(e);
    }
}

void CefBrowserHostWrapper::SetAccessibilityState(CefState accessibilityState)
{
    ThrowIfDisposed();

    _browserHost->SetAccessibilityState((cef_state_t)accessibilityState);
}

void CefBrowserHostWrapper::SetAutoResizeEnabled(bool enabled, Size minSize, Size maxSize)
{
    ThrowIfDisposed();

    _browserHost->SetAutoResizeEnabled(enabled, CefSize(minSize.Width, minSize.Height), CefSize(maxSize.Width, maxSize.Height));
}

void CefBrowserHostWrapper::Invalidate(PaintElementType type)
{
    ThrowIfDisposed();

    _browserHost->Invalidate((CefBrowserHost::PaintElementType)type);
}

void CefBrowserHostWrapper::ImeSetComposition(String^ text, cli::array<CompositionUnderline>^ underlines, Nullable<CefSharp::Structs::Range> replacementRange, Nullable<CefSharp::Structs::Range> selectionRange)
{
    ThrowIfDisposed();

    std::vector<CefCompositionUnderline> underlinesVector = std::vector<CefCompositionUnderline>();
    CefRange repRange;
    CefRange selRange;

    if (underlines != nullptr && underlines->Length > 0)
    {
        for each (CompositionUnderline underline in underlines)
        {
            auto c = CefCompositionUnderline();
            c.range = CefRange(underline.Range.From, underline.Range.To);
            c.color = underline.Color;
            c.background_color = underline.BackgroundColor;
            c.thick = (int)underline.Thick;
            c.style = (cef_composition_underline_style_t)underline.Style;
            underlinesVector.push_back(c);
        }
    }

    if (replacementRange.HasValue)
    {
        repRange = CefRange(replacementRange.Value.From, replacementRange.Value.To);
    }

    if (selectionRange.HasValue)
    {
        selRange = CefRange(selectionRange.Value.From, selectionRange.Value.To);
    }

    _browserHost->ImeSetComposition(StringUtils::ToNative(text), underlinesVector, repRange, selRange);
}

void CefBrowserHostWrapper::ImeCommitText(String^ text, Nullable<CefSharp::Structs::Range> replacementRange, int relativeCursorPos)
{
    ThrowIfDisposed();

    CefRange repRange;

    if (replacementRange.HasValue)
    {
        repRange = CefRange(replacementRange.Value.From, replacementRange.Value.To);
    }

    _browserHost->ImeCommitText(StringUtils::ToNative(text), repRange, relativeCursorPos);
}

void CefBrowserHostWrapper::ImeFinishComposingText(bool keepSelection)
{
    ThrowIfDisposed();

    _browserHost->ImeFinishComposingText(keepSelection);
}

void CefBrowserHostWrapper::ImeCancelComposition()
{
    ThrowIfDisposed();

    _browserHost->ImeCancelComposition();
}

void CefBrowserHostWrapper::SendMouseClickEvent(MouseEvent mouseEvent, MouseButtonType mouseButtonType, bool mouseUp, int clickCount)
{
    ThrowIfDisposed();

    CefMouseEvent m;
    m.x = mouseEvent.X;
    m.y = mouseEvent.Y;
    m.modifiers = (uint32_t)mouseEvent.Modifiers;

    _browserHost->SendMouseClickEvent(m, (CefBrowserHost::MouseButtonType) mouseButtonType, mouseUp, clickCount);
}

void CefBrowserHostWrapper::SendMouseMoveEvent(MouseEvent mouseEvent, bool mouseLeave)
{
    ThrowIfDisposed();

    CefMouseEvent m;
    m.x = mouseEvent.X;
    m.y = mouseEvent.Y;
    m.modifiers = (uint32_t)mouseEvent.Modifiers;

    _browserHost->SendMouseMoveEvent(m, mouseLeave);
}

void CefBrowserHostWrapper::WasResized()
{
    ThrowIfDisposed();

    _browserHost->WasResized();
}

void CefBrowserHostWrapper::WasHidden(bool hidden)
{
    ThrowIfDisposed();

    _browserHost->WasHidden(hidden);
}

void CefBrowserHostWrapper::GetNavigationEntries(INavigationEntryVisitor^ visitor, bool currentOnly)
{
    ThrowIfDisposed();

    auto navEntryVisitor = new CefNavigationEntryVisitorAdapter(visitor);

    _browserHost->GetNavigationEntries(navEntryVisitor, currentOnly);
}

NavigationEntry^ CefBrowserHostWrapper::GetVisibleNavigationEntry()
{
    ThrowIfDisposed();

    ThrowIfExecutedOnNonCefUiThread();

    auto entry = _browserHost->GetVisibleNavigationEntry();

    return TypeConversion::FromNative(entry, true);
}

void CefBrowserHostWrapper::NotifyMoveOrResizeStarted()
{
    ThrowIfDisposed();

    _browserHost->NotifyMoveOrResizeStarted();
}

void CefBrowserHostWrapper::NotifyScreenInfoChanged()
{
    ThrowIfDisposed();

    _browserHost->NotifyScreenInfoChanged();
}

int CefBrowserHostWrapper::WindowlessFrameRate::get()
{
    ThrowIfDisposed();

    return _browserHost->GetWindowlessFrameRate();
}

void CefBrowserHostWrapper::WindowlessFrameRate::set(int val)
{
    ThrowIfDisposed();

    _browserHost->SetWindowlessFrameRate(val);
}

bool CefBrowserHostWrapper::WindowRenderingDisabled::get()
{
    ThrowIfDisposed();

    return _browserHost->IsWindowRenderingDisabled();
}

bool CefBrowserHostWrapper::IsAudioMuted::get()
{
    ThrowIfDisposed();

    ThrowIfExecutedOnNonCefUiThread();

    return _browserHost->IsAudioMuted();
}

void CefBrowserHostWrapper::SetAudioMuted(bool mute)
{
    ThrowIfDisposed();

    _browserHost->SetAudioMuted(mute);
}

IntPtr CefBrowserHostWrapper::GetOpenerWindowHandle()
{
    ThrowIfDisposed();

    return IntPtr(_browserHost->GetOpenerWindowHandle());
}

void CefBrowserHostWrapper::SendExternalBeginFrame()
{
    ThrowIfDisposed();

    _browserHost->SendExternalBeginFrame();
}

void CefBrowserHostWrapper::SendCaptureLostEvent()
{
    ThrowIfDisposed();

    _browserHost->SendCaptureLostEvent();
}


IRequestContext^ CefBrowserHostWrapper::RequestContext::get()
{
    ThrowIfDisposed();

    return gcnew CefSharp::Core::RequestContext(_browserHost->GetRequestContext());
}

CefMouseEvent CefBrowserHostWrapper::GetCefMouseEvent(MouseEvent mouseEvent)
{
    CefMouseEvent cefMouseEvent;
    cefMouseEvent.x = mouseEvent.X;
    cefMouseEvent.y = mouseEvent.Y;
    cefMouseEvent.modifiers = (uint32_t)mouseEvent.Modifiers;
    return cefMouseEvent;
}

//Code imported from
//https://bitbucket.org/chromiumembedded/branches-2062-cef3/src/a073e92426b3967f1fc2f1d3fd7711d809eeb03a/tests/cefclient/cefclient_osr_widget_win.cpp?at=master#cl-361
int CefBrowserHostWrapper::GetCefKeyboardModifiers(WPARAM wparam, LPARAM lparam)
{
    int modifiers = 0;
    if (IsKeyDown(VK_SHIFT))
        modifiers |= EVENTFLAG_SHIFT_DOWN;
    if (IsKeyDown(VK_CONTROL))
        modifiers |= EVENTFLAG_CONTROL_DOWN;
    if (IsKeyDown(VK_MENU))
        modifiers |= EVENTFLAG_ALT_DOWN;

    // Low bit set from GetKeyState indicates "toggled".
    if (::GetKeyState(VK_NUMLOCK) & 1)
        modifiers |= EVENTFLAG_NUM_LOCK_ON;
    if (::GetKeyState(VK_CAPITAL) & 1)
        modifiers |= EVENTFLAG_CAPS_LOCK_ON;

    switch (wparam)
    {
    case VK_RETURN:
        if ((lparam >> 16) & KF_EXTENDED)
            modifiers |= EVENTFLAG_IS_KEY_PAD;
        break;
    case VK_INSERT:
    case VK_DELETE:
    case VK_HOME:
    case VK_END:
    case VK_PRIOR:
    case VK_NEXT:
    case VK_UP:
    case VK_DOWN:
    case VK_LEFT:
    case VK_RIGHT:
        if (!((lparam >> 16) & KF_EXTENDED))
            modifiers |= EVENTFLAG_IS_KEY_PAD;
        break;
    case VK_NUMLOCK:
    case VK_NUMPAD0:
    case VK_NUMPAD1:
    case VK_NUMPAD2:
    case VK_NUMPAD3:
    case VK_NUMPAD4:
    case VK_NUMPAD5:
    case VK_NUMPAD6:
    case VK_NUMPAD7:
    case VK_NUMPAD8:
    case VK_NUMPAD9:
    case VK_DIVIDE:
    case VK_MULTIPLY:
    case VK_SUBTRACT:
    case VK_ADD:
    case VK_DECIMAL:
    case VK_CLEAR:
        modifiers |= EVENTFLAG_IS_KEY_PAD;
        break;
    case VK_SHIFT:
        if (IsKeyDown(VK_LSHIFT))
            modifiers |= EVENTFLAG_IS_LEFT;
        else if (IsKeyDown(VK_RSHIFT))
            modifiers |= EVENTFLAG_IS_RIGHT;
        break;
    case VK_CONTROL:
        if (IsKeyDown(VK_LCONTROL))
            modifiers |= EVENTFLAG_IS_LEFT;
        else if (IsKeyDown(VK_RCONTROL))
            modifiers |= EVENTFLAG_IS_RIGHT;
        break;
    case VK_MENU:
        if (IsKeyDown(VK_LMENU))
            modifiers |= EVENTFLAG_IS_LEFT;
        else if (IsKeyDown(VK_RMENU))
            modifiers |= EVENTFLAG_IS_RIGHT;
        break;
    case VK_LWIN:
        modifiers |= EVENTFLAG_IS_LEFT;
        break;
    case VK_RWIN:
        modifiers |= EVENTFLAG_IS_RIGHT;
        break;
    }
    return modifiers;
}
