|  | /*
  ==============================================================================
   This file is part of the JUCE library.
   Copyright (c) 2020 - Raw Material Software Limited
   JUCE is an open source library subject to commercial or open-source
   licensing.
   By using JUCE, you agree to the terms of both the JUCE 6 End-User License
   Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
   End User License Agreement: www.juce.com/juce-6-licence
   Privacy Policy: www.juce.com/juce-privacy-policy
   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).
   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.
  ==============================================================================
*/
#if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client
 #include <juce_audio_plugin_client/AAX/juce_AAX_Modifier_Injector.h>
#endif
namespace juce
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wcast-function-type")
#undef GetSystemMetrics // multimon overrides this for some reason and causes a mess..
// these are in the windows SDK, but need to be repeated here for GCC..
#ifndef GET_APPCOMMAND_LPARAM
 #define GET_APPCOMMAND_LPARAM(lParam)     ((short) (HIWORD (lParam) & ~FAPPCOMMAND_MASK))
 #define FAPPCOMMAND_MASK                  0xF000
 #define APPCOMMAND_MEDIA_NEXTTRACK        11
 #define APPCOMMAND_MEDIA_PREVIOUSTRACK    12
 #define APPCOMMAND_MEDIA_STOP             13
 #define APPCOMMAND_MEDIA_PLAY_PAUSE       14
#endif
#ifndef WM_APPCOMMAND
 #define WM_APPCOMMAND                     0x0319
#endif
void juce_repeatLastProcessPriority();
bool juce_isRunningInWine();
using CheckEventBlockedByModalComps = bool (*) (const MSG&);
extern CheckEventBlockedByModalComps isEventBlockedByModalComps;
static bool shouldDeactivateTitleBar = true;
void* getUser32Function (const char*);
#if JUCE_DEBUG
 int numActiveScopedDpiAwarenessDisablers = 0;
 bool isInScopedDPIAwarenessDisabler() { return numActiveScopedDpiAwarenessDisablers > 0; }
 extern HWND juce_messageWindowHandle;
#endif
struct ScopedDeviceContext
{
    explicit ScopedDeviceContext (HWND h)
        : hwnd (h), dc (GetDC (hwnd))
    {
    }
    ~ScopedDeviceContext()
    {
        ReleaseDC (hwnd, dc);
    }
    HWND hwnd;
    HDC dc;
    JUCE_DECLARE_NON_COPYABLE (ScopedDeviceContext)
    JUCE_DECLARE_NON_MOVEABLE (ScopedDeviceContext)
};
//==============================================================================
#ifndef WM_TOUCH
 enum
 {
     WM_TOUCH         = 0x0240,
     TOUCHEVENTF_MOVE = 0x0001,
     TOUCHEVENTF_DOWN = 0x0002,
     TOUCHEVENTF_UP   = 0x0004
 };
 typedef HANDLE HTOUCHINPUT;
 typedef HANDLE HGESTUREINFO;
 struct TOUCHINPUT
 {
     LONG         x;
     LONG         y;
     HANDLE       hSource;
     DWORD        dwID;
     DWORD        dwFlags;
     DWORD        dwMask;
     DWORD        dwTime;
     ULONG_PTR    dwExtraInfo;
     DWORD        cxContact;
     DWORD        cyContact;
 };
 struct GESTUREINFO
 {
     UINT         cbSize;
     DWORD        dwFlags;
     DWORD        dwID;
     HWND         hwndTarget;
     POINTS       ptsLocation;
     DWORD        dwInstanceID;
     DWORD        dwSequenceID;
     ULONGLONG    ullArguments;
     UINT         cbExtraArgs;
 };
#endif
#ifndef WM_NCPOINTERUPDATE
 enum
 {
     WM_NCPOINTERUPDATE       = 0x241,
     WM_NCPOINTERDOWN         = 0x242,
     WM_NCPOINTERUP           = 0x243,
     WM_POINTERUPDATE         = 0x245,
     WM_POINTERDOWN           = 0x246,
     WM_POINTERUP             = 0x247,
     WM_POINTERENTER          = 0x249,
     WM_POINTERLEAVE          = 0x24A,
     WM_POINTERACTIVATE       = 0x24B,
     WM_POINTERCAPTURECHANGED = 0x24C,
     WM_TOUCHHITTESTING       = 0x24D,
     WM_POINTERWHEEL          = 0x24E,
     WM_POINTERHWHEEL         = 0x24F,
     WM_POINTERHITTEST        = 0x250
 };
 enum
 {
     PT_TOUCH = 0x00000002,
     PT_PEN   = 0x00000003
 };
 enum POINTER_BUTTON_CHANGE_TYPE
 {
     POINTER_CHANGE_NONE,
     POINTER_CHANGE_FIRSTBUTTON_DOWN,
     POINTER_CHANGE_FIRSTBUTTON_UP,
     POINTER_CHANGE_SECONDBUTTON_DOWN,
     POINTER_CHANGE_SECONDBUTTON_UP,
     POINTER_CHANGE_THIRDBUTTON_DOWN,
     POINTER_CHANGE_THIRDBUTTON_UP,
     POINTER_CHANGE_FOURTHBUTTON_DOWN,
     POINTER_CHANGE_FOURTHBUTTON_UP,
     POINTER_CHANGE_FIFTHBUTTON_DOWN,
     POINTER_CHANGE_FIFTHBUTTON_UP
 };
 enum
 {
     PEN_MASK_NONE      = 0x00000000,
     PEN_MASK_PRESSURE  = 0x00000001,
     PEN_MASK_ROTATION  = 0x00000002,
     PEN_MASK_TILT_X    = 0x00000004,
     PEN_MASK_TILT_Y    = 0x00000008
 };
 enum
 {
     TOUCH_MASK_NONE        = 0x00000000,
     TOUCH_MASK_CONTACTAREA = 0x00000001,
     TOUCH_MASK_ORIENTATION = 0x00000002,
     TOUCH_MASK_PRESSURE    = 0x00000004
 };
 enum
 {
     POINTER_FLAG_NONE           = 0x00000000,
     POINTER_FLAG_NEW            = 0x00000001,
     POINTER_FLAG_INRANGE        = 0x00000002,
     POINTER_FLAG_INCONTACT      = 0x00000004,
     POINTER_FLAG_FIRSTBUTTON    = 0x00000010,
     POINTER_FLAG_SECONDBUTTON   = 0x00000020,
     POINTER_FLAG_THIRDBUTTON    = 0x00000040,
     POINTER_FLAG_FOURTHBUTTON   = 0x00000080,
     POINTER_FLAG_FIFTHBUTTON    = 0x00000100,
     POINTER_FLAG_PRIMARY        = 0x00002000,
     POINTER_FLAG_CONFIDENCE     = 0x00004000,
     POINTER_FLAG_CANCELED       = 0x00008000,
     POINTER_FLAG_DOWN           = 0x00010000,
     POINTER_FLAG_UPDATE         = 0x00020000,
     POINTER_FLAG_UP             = 0x00040000,
     POINTER_FLAG_WHEEL          = 0x00080000,
     POINTER_FLAG_HWHEEL         = 0x00100000,
     POINTER_FLAG_CAPTURECHANGED = 0x00200000,
     POINTER_FLAG_HASTRANSFORM   = 0x00400000
 };
 typedef DWORD  POINTER_INPUT_TYPE;
 typedef UINT32 POINTER_FLAGS;
 typedef UINT32 PEN_FLAGS;
 typedef UINT32 PEN_MASK;
 typedef UINT32 TOUCH_FLAGS;
 typedef UINT32 TOUCH_MASK;
 struct POINTER_INFO
 {
     POINTER_INPUT_TYPE         pointerType;
     UINT32                     pointerId;
     UINT32                     frameId;
     POINTER_FLAGS              pointerFlags;
     HANDLE                     sourceDevice;
     HWND                       hwndTarget;
     POINT                      ptPixelLocation;
     POINT                      ptHimetricLocation;
     POINT                      ptPixelLocationRaw;
     POINT                      ptHimetricLocationRaw;
     DWORD                      dwTime;
     UINT32                     historyCount;
     INT32                      InputData;
     DWORD                      dwKeyStates;
     UINT64                     PerformanceCount;
     POINTER_BUTTON_CHANGE_TYPE ButtonChangeType;
 };
 struct POINTER_TOUCH_INFO
 {
     POINTER_INFO    pointerInfo;
     TOUCH_FLAGS     touchFlags;
     TOUCH_MASK      touchMask;
     RECT            rcContact;
     RECT            rcContactRaw;
     UINT32          orientation;
     UINT32          pressure;
 };
 struct POINTER_PEN_INFO
 {
     POINTER_INFO    pointerInfo;
     PEN_FLAGS       penFlags;
     PEN_MASK        penMask;
     UINT32          pressure;
     UINT32          rotation;
     INT32           tiltX;
     INT32           tiltY;
 };
 #define GET_POINTERID_WPARAM(wParam)    (LOWORD(wParam))
#endif
#ifndef MONITOR_DPI_TYPE
 enum Monitor_DPI_Type
 {
     MDT_Effective_DPI = 0,
     MDT_Angular_DPI   = 1,
     MDT_Raw_DPI       = 2,
     MDT_Default       = MDT_Effective_DPI
 };
#endif
#ifndef DPI_AWARENESS
 enum DPI_Awareness
 {
     DPI_Awareness_Invalid           = -1,
     DPI_Awareness_Unaware           = 0,
     DPI_Awareness_System_Aware      = 1,
     DPI_Awareness_Per_Monitor_Aware = 2
 };
#endif
#ifndef USER_DEFAULT_SCREEN_DPI
 #define USER_DEFAULT_SCREEN_DPI 96
#endif
#ifndef _DPI_AWARENESS_CONTEXTS_
 typedef HANDLE DPI_AWARENESS_CONTEXT;
 #define DPI_AWARENESS_CONTEXT_UNAWARE              ((DPI_AWARENESS_CONTEXT) - 1)
 #define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE         ((DPI_AWARENESS_CONTEXT) - 2)
 #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE    ((DPI_AWARENESS_CONTEXT) - 3)
 #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT) - 4)
#endif
// Some versions of the Windows 10 SDK define _DPI_AWARENESS_CONTEXTS_ but not
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
#ifndef DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
 #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT) - 4)
#endif
//==============================================================================
using RegisterTouchWindowFunc    = BOOL (WINAPI*) (HWND, ULONG);
using GetTouchInputInfoFunc      = BOOL (WINAPI*) (HTOUCHINPUT, UINT, TOUCHINPUT*, int);
using CloseTouchInputHandleFunc  = BOOL (WINAPI*) (HTOUCHINPUT);
using GetGestureInfoFunc         = BOOL (WINAPI*) (HGESTUREINFO, GESTUREINFO*);
static RegisterTouchWindowFunc   registerTouchWindow   = nullptr;
static GetTouchInputInfoFunc     getTouchInputInfo     = nullptr;
static CloseTouchInputHandleFunc closeTouchInputHandle = nullptr;
static GetGestureInfoFunc        getGestureInfo        = nullptr;
static bool hasCheckedForMultiTouch = false;
static bool canUseMultiTouch()
{
    if (registerTouchWindow == nullptr && ! hasCheckedForMultiTouch)
    {
        hasCheckedForMultiTouch = true;
        registerTouchWindow   = (RegisterTouchWindowFunc)   getUser32Function ("RegisterTouchWindow");
        getTouchInputInfo     = (GetTouchInputInfoFunc)     getUser32Function ("GetTouchInputInfo");
        closeTouchInputHandle = (CloseTouchInputHandleFunc) getUser32Function ("CloseTouchInputHandle");
        getGestureInfo        = (GetGestureInfoFunc)        getUser32Function ("GetGestureInfo");
    }
    return registerTouchWindow != nullptr;
}
//==============================================================================
using GetPointerTypeFunc       =  BOOL (WINAPI*) (UINT32, POINTER_INPUT_TYPE*);
using GetPointerTouchInfoFunc  =  BOOL (WINAPI*) (UINT32, POINTER_TOUCH_INFO*);
using GetPointerPenInfoFunc    =  BOOL (WINAPI*) (UINT32, POINTER_PEN_INFO*);
static GetPointerTypeFunc      getPointerTypeFunction = nullptr;
static GetPointerTouchInfoFunc getPointerTouchInfo    = nullptr;
static GetPointerPenInfoFunc   getPointerPenInfo      = nullptr;
static bool canUsePointerAPI = false;
static void checkForPointerAPI()
{
    getPointerTypeFunction = (GetPointerTypeFunc)      getUser32Function ("GetPointerType");
    getPointerTouchInfo    = (GetPointerTouchInfoFunc) getUser32Function ("GetPointerTouchInfo");
    getPointerPenInfo      = (GetPointerPenInfoFunc)   getUser32Function ("GetPointerPenInfo");
    canUsePointerAPI = (getPointerTypeFunction != nullptr
                     && getPointerTouchInfo    != nullptr
                     && getPointerPenInfo      != nullptr);
}
//==============================================================================
using SetProcessDPIAwareFunc                   = BOOL                  (WINAPI*) ();
using SetProcessDPIAwarenessContextFunc        = BOOL                  (WINAPI*) (DPI_AWARENESS_CONTEXT);
using SetProcessDPIAwarenessFunc               = HRESULT               (WINAPI*) (DPI_Awareness);
using SetThreadDPIAwarenessContextFunc         = DPI_AWARENESS_CONTEXT (WINAPI*) (DPI_AWARENESS_CONTEXT);
using GetDPIForWindowFunc                      = UINT                  (WINAPI*) (HWND);
using GetDPIForMonitorFunc                     = HRESULT               (WINAPI*) (HMONITOR, Monitor_DPI_Type, UINT*, UINT*);
using GetSystemMetricsForDpiFunc               = int                   (WINAPI*) (int, UINT);
using GetProcessDPIAwarenessFunc               = HRESULT               (WINAPI*) (HANDLE, DPI_Awareness*);
using GetWindowDPIAwarenessContextFunc         = DPI_AWARENESS_CONTEXT (WINAPI*) (HWND);
using GetThreadDPIAwarenessContextFunc         = DPI_AWARENESS_CONTEXT (WINAPI*) ();
using GetAwarenessFromDpiAwarenessContextFunc  = DPI_Awareness         (WINAPI*) (DPI_AWARENESS_CONTEXT);
using EnableNonClientDPIScalingFunc            = BOOL                  (WINAPI*) (HWND);
static SetProcessDPIAwareFunc                  setProcessDPIAware                  = nullptr;
static SetProcessDPIAwarenessContextFunc       setProcessDPIAwarenessContext       = nullptr;
static SetProcessDPIAwarenessFunc              setProcessDPIAwareness              = nullptr;
static SetThreadDPIAwarenessContextFunc        setThreadDPIAwarenessContext        = nullptr;
static GetDPIForMonitorFunc                    getDPIForMonitor                    = nullptr;
static GetDPIForWindowFunc                     getDPIForWindow                     = nullptr;
static GetProcessDPIAwarenessFunc              getProcessDPIAwareness              = nullptr;
static GetWindowDPIAwarenessContextFunc        getWindowDPIAwarenessContext        = nullptr;
static GetThreadDPIAwarenessContextFunc        getThreadDPIAwarenessContext        = nullptr;
static GetAwarenessFromDpiAwarenessContextFunc getAwarenessFromDPIAwarenessContext = nullptr;
static EnableNonClientDPIScalingFunc           enableNonClientDPIScaling           = nullptr;
static bool hasCheckedForDPIAwareness = false;
static void loadDPIAwarenessFunctions()
{
    setProcessDPIAware = (SetProcessDPIAwareFunc) getUser32Function ("SetProcessDPIAware");
    constexpr auto shcore = "SHCore.dll";
    LoadLibraryA (shcore);
    const auto shcoreModule = GetModuleHandleA (shcore);
    if (shcoreModule == nullptr)
        return;
    getDPIForMonitor                    = (GetDPIForMonitorFunc) GetProcAddress (shcoreModule, "GetDpiForMonitor");
    setProcessDPIAwareness              = (SetProcessDPIAwarenessFunc) GetProcAddress (shcoreModule, "SetProcessDpiAwareness");
   #if JUCE_WIN_PER_MONITOR_DPI_AWARE
    getDPIForWindow                     = (GetDPIForWindowFunc) getUser32Function ("GetDpiForWindow");
    getProcessDPIAwareness              = (GetProcessDPIAwarenessFunc) GetProcAddress (shcoreModule, "GetProcessDpiAwareness");
    getWindowDPIAwarenessContext        = (GetWindowDPIAwarenessContextFunc) getUser32Function ("GetWindowDpiAwarenessContext");
    setThreadDPIAwarenessContext        = (SetThreadDPIAwarenessContextFunc) getUser32Function ("SetThreadDpiAwarenessContext");
    getThreadDPIAwarenessContext        = (GetThreadDPIAwarenessContextFunc) getUser32Function ("GetThreadDpiAwarenessContext");
    getAwarenessFromDPIAwarenessContext = (GetAwarenessFromDpiAwarenessContextFunc) getUser32Function ("GetAwarenessFromDpiAwarenessContext");
    setProcessDPIAwarenessContext       = (SetProcessDPIAwarenessContextFunc) getUser32Function ("SetProcessDpiAwarenessContext");
    enableNonClientDPIScaling           = (EnableNonClientDPIScalingFunc) getUser32Function ("EnableNonClientDpiScaling");
   #endif
}
static void setDPIAwareness()
{
    if (hasCheckedForDPIAwareness)
        return;
    hasCheckedForDPIAwareness = true;
    if (! JUCEApplicationBase::isStandaloneApp())
        return;
    loadDPIAwarenessFunctions();
    if (setProcessDPIAwarenessContext != nullptr
        && setProcessDPIAwarenessContext (DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2))
        return;
    if (setProcessDPIAwareness != nullptr && enableNonClientDPIScaling != nullptr
        && SUCCEEDED (setProcessDPIAwareness (DPI_Awareness::DPI_Awareness_Per_Monitor_Aware)))
        return;
    if (setProcessDPIAwareness != nullptr && getDPIForMonitor != nullptr
        && SUCCEEDED (setProcessDPIAwareness (DPI_Awareness::DPI_Awareness_System_Aware)))
        return;
    if (setProcessDPIAware != nullptr)
        setProcessDPIAware();
}
static bool isPerMonitorDPIAwareProcess()
{
   #if ! JUCE_WIN_PER_MONITOR_DPI_AWARE
    return false;
   #else
    static bool dpiAware = []() -> bool
    {
        setDPIAwareness();
        if (! JUCEApplication::isStandaloneApp())
            return false;
        if (getProcessDPIAwareness == nullptr)
            return false;
        DPI_Awareness context;
        getProcessDPIAwareness (nullptr, &context);
        return context == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware;
    }();
    return dpiAware;
   #endif
}
static bool isPerMonitorDPIAwareWindow (HWND nativeWindow)
{
   #if ! JUCE_WIN_PER_MONITOR_DPI_AWARE
    ignoreUnused (nativeWindow);
    return false;
   #else
    setDPIAwareness();
    if (getWindowDPIAwarenessContext != nullptr
        && getAwarenessFromDPIAwarenessContext != nullptr)
    {
        return (getAwarenessFromDPIAwarenessContext (getWindowDPIAwarenessContext (nativeWindow))
                  == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware);
    }
    return isPerMonitorDPIAwareProcess();
   #endif
}
static bool isPerMonitorDPIAwareThread()
{
   #if ! JUCE_WIN_PER_MONITOR_DPI_AWARE
    return false;
   #else
    setDPIAwareness();
    if (getThreadDPIAwarenessContext != nullptr
        && getAwarenessFromDPIAwarenessContext != nullptr)
    {
        return (getAwarenessFromDPIAwarenessContext (getThreadDPIAwarenessContext())
                  == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware);
    }
    return isPerMonitorDPIAwareProcess();
   #endif
}
static double getGlobalDPI()
{
    setDPIAwareness();
    ScopedDeviceContext deviceContext { nullptr };
    return (GetDeviceCaps (deviceContext.dc, LOGPIXELSX) + GetDeviceCaps (deviceContext.dc, LOGPIXELSY)) / 2.0;
}
//==============================================================================
class ScopedThreadDPIAwarenessSetter::NativeImpl
{
public:
    explicit NativeImpl (HWND nativeWindow)
    {
        ignoreUnused (nativeWindow);
       #if JUCE_WIN_PER_MONITOR_DPI_AWARE
        if (auto* functionSingleton = FunctionSingleton::getInstance())
        {
            if (! functionSingleton->isLoaded())
                return;
            auto dpiAwareWindow = (functionSingleton->getAwarenessFromContext (functionSingleton->getWindowAwareness (nativeWindow))
                                   == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware);
            auto dpiAwareThread = (functionSingleton->getAwarenessFromContext (functionSingleton->getThreadAwareness())
                                   == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware);
            if (dpiAwareWindow && ! dpiAwareThread)
                oldContext = functionSingleton->setThreadAwareness (DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
            else if (! dpiAwareWindow && dpiAwareThread)
                oldContext = functionSingleton->setThreadAwareness (DPI_AWARENESS_CONTEXT_UNAWARE);
        }
       #endif
    }
    ~NativeImpl()
    {
        if (oldContext != nullptr)
            if (auto* functionSingleton = FunctionSingleton::getInstance())
                functionSingleton->setThreadAwareness (oldContext);
    }
private:
    struct FunctionSingleton  : public DeletedAtShutdown
    {
        FunctionSingleton() = default;
        ~FunctionSingleton() override { clearSingletonInstance(); }
        SetThreadDPIAwarenessContextFunc setThreadAwareness             = (SetThreadDPIAwarenessContextFunc) getUser32Function ("SetThreadDpiAwarenessContext");
        GetWindowDPIAwarenessContextFunc getWindowAwareness             = (GetWindowDPIAwarenessContextFunc) getUser32Function ("GetWindowDpiAwarenessContext");
        GetThreadDPIAwarenessContextFunc getThreadAwareness             = (GetThreadDPIAwarenessContextFunc) getUser32Function ("GetThreadDpiAwarenessContext");
        GetAwarenessFromDpiAwarenessContextFunc getAwarenessFromContext = (GetAwarenessFromDpiAwarenessContextFunc) getUser32Function ("GetAwarenessFromDpiAwarenessContext");
        bool isLoaded() const noexcept
        {
            return setThreadAwareness != nullptr
                && getWindowAwareness != nullptr
                && getThreadAwareness != nullptr
                && getAwarenessFromContext != nullptr;
        }
        JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (FunctionSingleton)
        JUCE_DECLARE_NON_COPYABLE (FunctionSingleton)
        JUCE_DECLARE_NON_MOVEABLE (FunctionSingleton)
    };
    DPI_AWARENESS_CONTEXT oldContext = nullptr;
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeImpl)
    JUCE_DECLARE_NON_MOVEABLE (NativeImpl)
};
JUCE_IMPLEMENT_SINGLETON (ScopedThreadDPIAwarenessSetter::NativeImpl::FunctionSingleton)
ScopedThreadDPIAwarenessSetter::ScopedThreadDPIAwarenessSetter (void* nativeWindow)
{
    pimpl = std::make_unique<NativeImpl> ((HWND) nativeWindow);
}
ScopedThreadDPIAwarenessSetter::~ScopedThreadDPIAwarenessSetter() = default;
ScopedDPIAwarenessDisabler::ScopedDPIAwarenessDisabler()
{
    if (! isPerMonitorDPIAwareThread())
        return;
    if (setThreadDPIAwarenessContext != nullptr)
    {
        previousContext = setThreadDPIAwarenessContext (DPI_AWARENESS_CONTEXT_UNAWARE);
       #if JUCE_DEBUG
        ++numActiveScopedDpiAwarenessDisablers;
       #endif
    }
}
ScopedDPIAwarenessDisabler::~ScopedDPIAwarenessDisabler()
{
    if (previousContext != nullptr)
    {
        setThreadDPIAwarenessContext ((DPI_AWARENESS_CONTEXT) previousContext);
       #if JUCE_DEBUG
        --numActiveScopedDpiAwarenessDisablers;
       #endif
    }
}
//==============================================================================
using SettingChangeCallbackFunc = void (*)(void);
extern SettingChangeCallbackFunc settingChangeCallback;
//==============================================================================
static Rectangle<int> rectangleFromRECT (RECT r) noexcept    { return { r.left, r.top, r.right - r.left, r.bottom - r.top }; }
static RECT RECTFromRectangle (Rectangle<int> r) noexcept    { return { r.getX(), r.getY(), r.getRight(), r.getBottom() }; }
static Point<int> pointFromPOINT (POINT p) noexcept          { return { p.x, p.y }; }
static POINT POINTFromPoint (Point<int> p) noexcept          { return { p.x, p.y }; }
//==============================================================================
static const Displays::Display* getCurrentDisplayFromScaleFactor (HWND hwnd);
template <typename ValueType>
static Rectangle<ValueType> convertPhysicalScreenRectangleToLogical (Rectangle<ValueType> r, HWND h) noexcept
{
    if (isPerMonitorDPIAwareWindow (h))
        return Desktop::getInstance().getDisplays().physicalToLogical (r, getCurrentDisplayFromScaleFactor (h));
    return r;
}
template <typename ValueType>
static Rectangle<ValueType> convertLogicalScreenRectangleToPhysical (Rectangle<ValueType> r, HWND h) noexcept
{
    if (isPerMonitorDPIAwareWindow (h))
        return Desktop::getInstance().getDisplays().logicalToPhysical (r, getCurrentDisplayFromScaleFactor (h));
    return r;
}
static Point<int> convertPhysicalScreenPointToLogical (Point<int> p, HWND h) noexcept
{
    if (isPerMonitorDPIAwareWindow (h))
        return Desktop::getInstance().getDisplays().physicalToLogical (p, getCurrentDisplayFromScaleFactor (h));
    return p;
}
static Point<int> convertLogicalScreenPointToPhysical (Point<int> p, HWND h) noexcept
{
    if (isPerMonitorDPIAwareWindow (h))
        return Desktop::getInstance().getDisplays().logicalToPhysical (p, getCurrentDisplayFromScaleFactor (h));
    return p;
}
JUCE_API double getScaleFactorForWindow (HWND h)
{
    // NB. Using a local function here because we need to call this method from the plug-in wrappers
    // which don't load the DPI-awareness functions on startup
    static GetDPIForWindowFunc localGetDPIForWindow = nullptr;
    static bool hasChecked = false;
    if (! hasChecked)
    {
        hasChecked = true;
        if (localGetDPIForWindow == nullptr)
            localGetDPIForWindow = (GetDPIForWindowFunc) getUser32Function ("GetDpiForWindow");
    }
    if (localGetDPIForWindow != nullptr)
        return (double) localGetDPIForWindow (h) / USER_DEFAULT_SCREEN_DPI;
    return 1.0;
 }
//==============================================================================
static void setWindowPos (HWND hwnd, Rectangle<int> bounds, UINT flags, bool adjustTopLeft = false)
{
    ScopedThreadDPIAwarenessSetter setter { hwnd };
    if (isPerMonitorDPIAwareWindow (hwnd))
    {
        if (adjustTopLeft)
            bounds = convertLogicalScreenRectangleToPhysical (bounds, hwnd)
                      .withPosition (Desktop::getInstance().getDisplays().logicalToPhysical (bounds.getTopLeft()));
        else
            bounds = convertLogicalScreenRectangleToPhysical (bounds, hwnd);
    }
    SetWindowPos (hwnd, nullptr, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), flags);
}
static RECT getWindowScreenRect (HWND hwnd)
{
    ScopedThreadDPIAwarenessSetter setter { hwnd };
    RECT rect;
    GetWindowRect (hwnd, &rect);
    return rect;
}
static RECT getWindowClientRect (HWND hwnd)
{
    auto rect = getWindowScreenRect (hwnd);
    if (auto parentH = GetParent (hwnd))
    {
        ScopedThreadDPIAwarenessSetter setter { hwnd };
        MapWindowPoints (HWND_DESKTOP, parentH, (LPPOINT) &rect, 2);
    }
    return rect;
}
static void setWindowZOrder (HWND hwnd, HWND insertAfter)
{
    SetWindowPos (hwnd, insertAfter, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
}
//==============================================================================
#if ! JUCE_MINGW
extern RTL_OSVERSIONINFOW getWindowsVersionInfo();
#endif
double Desktop::getDefaultMasterScale()
{
    if (! JUCEApplicationBase::isStandaloneApp() || isPerMonitorDPIAwareProcess())
        return 1.0;
    return getGlobalDPI() / USER_DEFAULT_SCREEN_DPI;
}
bool Desktop::canUseSemiTransparentWindows() noexcept
{
    return true;
}
class Desktop::NativeDarkModeChangeDetectorImpl
{
public:
    NativeDarkModeChangeDetectorImpl()
    {
       #if ! JUCE_MINGW
        const auto winVer = getWindowsVersionInfo();
        if (winVer.dwMajorVersion >= 10 && winVer.dwBuildNumber >= 17763)
        {
            const auto uxtheme = "uxtheme.dll";
            LoadLibraryA (uxtheme);
            const auto uxthemeModule = GetModuleHandleA (uxtheme);
            if (uxthemeModule != nullptr)
            {
                shouldAppsUseDarkMode = (ShouldAppsUseDarkModeFunc) GetProcAddress (uxthemeModule, MAKEINTRESOURCEA (132));
                if (shouldAppsUseDarkMode != nullptr)
                    darkModeEnabled = shouldAppsUseDarkMode() && ! isHighContrast();
            }
        }
       #endif
    }
    bool isDarkModeEnabled() const noexcept  { return darkModeEnabled; }
private:
    static bool isHighContrast()
    {
        HIGHCONTRASTW highContrast {};
        if (SystemParametersInfoW (SPI_GETHIGHCONTRAST, sizeof (highContrast), &highContrast, false))
            return highContrast.dwFlags & HCF_HIGHCONTRASTON;
        return false;
    }
    static LRESULT CALLBACK callWndProc (int nCode, WPARAM wParam, LPARAM lParam)
    {
        auto* params = reinterpret_cast<CWPSTRUCT*> (lParam);
        if (nCode >= 0
            && params != nullptr
            && params->message == WM_SETTINGCHANGE
            && params->lParam != 0
            && CompareStringOrdinal (reinterpret_cast<LPWCH> (params->lParam), -1, L"ImmersiveColorSet", -1, true) == CSTR_EQUAL)
        {
            Desktop::getInstance().nativeDarkModeChangeDetectorImpl->colourSetChanged();
        }
        return CallNextHookEx ({}, nCode, wParam, lParam);
    }
    void colourSetChanged()
    {
        if (shouldAppsUseDarkMode != nullptr)
        {
            const auto wasDarkModeEnabled = std::exchange (darkModeEnabled, shouldAppsUseDarkMode() && ! isHighContrast());
            if (darkModeEnabled != wasDarkModeEnabled)
                Desktop::getInstance().darkModeChanged();
        }
    }
    using ShouldAppsUseDarkModeFunc = bool (WINAPI*)();
    ShouldAppsUseDarkModeFunc shouldAppsUseDarkMode = nullptr;
    bool darkModeEnabled = false;
    HHOOK hook { SetWindowsHookEx (WH_CALLWNDPROC,
                                   callWndProc,
                                   (HINSTANCE) juce::Process::getCurrentModuleInstanceHandle(),
                                   GetCurrentThreadId()) };
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeDarkModeChangeDetectorImpl)
};
std::unique_ptr<Desktop::NativeDarkModeChangeDetectorImpl> Desktop::createNativeDarkModeChangeDetectorImpl()
{
    return std::make_unique<NativeDarkModeChangeDetectorImpl>();
}
bool Desktop::isDarkModeActive() const
{
    return nativeDarkModeChangeDetectorImpl->isDarkModeEnabled();
}
Desktop::DisplayOrientation Desktop::getCurrentOrientation() const
{
    return upright;
}
int64 getMouseEventTime()
{
    static int64 eventTimeOffset = 0;
    static LONG lastMessageTime = 0;
    const LONG thisMessageTime = GetMessageTime();
    if (thisMessageTime < lastMessageTime || lastMessageTime == 0)
    {
        lastMessageTime = thisMessageTime;
        eventTimeOffset = Time::currentTimeMillis() - thisMessageTime;
    }
    return eventTimeOffset + thisMessageTime;
}
//==============================================================================
const int extendedKeyModifier               = 0x10000;
const int KeyPress::spaceKey                = VK_SPACE;
const int KeyPress::returnKey               = VK_RETURN;
const int KeyPress::escapeKey               = VK_ESCAPE;
const int KeyPress::backspaceKey            = VK_BACK;
const int KeyPress::deleteKey               = VK_DELETE         | extendedKeyModifier;
const int KeyPress::insertKey               = VK_INSERT         | extendedKeyModifier;
const int KeyPress::tabKey                  = VK_TAB;
const int KeyPress::leftKey                 = VK_LEFT           | extendedKeyModifier;
const int KeyPress::rightKey                = VK_RIGHT          | extendedKeyModifier;
const int KeyPress::upKey                   = VK_UP             | extendedKeyModifier;
const int KeyPress::downKey                 = VK_DOWN           | extendedKeyModifier;
const int KeyPress::homeKey                 = VK_HOME           | extendedKeyModifier;
const int KeyPress::endKey                  = VK_END            | extendedKeyModifier;
const int KeyPress::pageUpKey               = VK_PRIOR          | extendedKeyModifier;
const int KeyPress::pageDownKey             = VK_NEXT           | extendedKeyModifier;
const int KeyPress::F1Key                   = VK_F1             | extendedKeyModifier;
const int KeyPress::F2Key                   = VK_F2             | extendedKeyModifier;
const int KeyPress::F3Key                   = VK_F3             | extendedKeyModifier;
const int KeyPress::F4Key                   = VK_F4             | extendedKeyModifier;
const int KeyPress::F5Key                   = VK_F5             | extendedKeyModifier;
const int KeyPress::F6Key                   = VK_F6             | extendedKeyModifier;
const int KeyPress::F7Key                   = VK_F7             | extendedKeyModifier;
const int KeyPress::F8Key                   = VK_F8             | extendedKeyModifier;
const int KeyPress::F9Key                   = VK_F9             | extendedKeyModifier;
const int KeyPress::F10Key                  = VK_F10            | extendedKeyModifier;
const int KeyPress::F11Key                  = VK_F11            | extendedKeyModifier;
const int KeyPress::F12Key                  = VK_F12            | extendedKeyModifier;
const int KeyPress::F13Key                  = VK_F13            | extendedKeyModifier;
const int KeyPress::F14Key                  = VK_F14            | extendedKeyModifier;
const int KeyPress::F15Key                  = VK_F15            | extendedKeyModifier;
const int KeyPress::F16Key                  = VK_F16            | extendedKeyModifier;
const int KeyPress::F17Key                  = VK_F17            | extendedKeyModifier;
const int KeyPress::F18Key                  = VK_F18            | extendedKeyModifier;
const int KeyPress::F19Key                  = VK_F19            | extendedKeyModifier;
const int KeyPress::F20Key                  = VK_F20            | extendedKeyModifier;
const int KeyPress::F21Key                  = VK_F21            | extendedKeyModifier;
const int KeyPress::F22Key                  = VK_F22            | extendedKeyModifier;
const int KeyPress::F23Key                  = VK_F23            | extendedKeyModifier;
const int KeyPress::F24Key                  = VK_F24            | extendedKeyModifier;
const int KeyPress::F25Key                  = 0x31000;          // Windows doesn't support F-keys 25 or higher
const int KeyPress::F26Key                  = 0x31001;
const int KeyPress::F27Key                  = 0x31002;
const int KeyPress::F28Key                  = 0x31003;
const int KeyPress::F29Key                  = 0x31004;
const int KeyPress::F30Key                  = 0x31005;
const int KeyPress::F31Key                  = 0x31006;
const int KeyPress::F32Key                  = 0x31007;
const int KeyPress::F33Key                  = 0x31008;
const int KeyPress::F34Key                  = 0x31009;
const int KeyPress::F35Key                  = 0x3100a;
const int KeyPress::numberPad0              = VK_NUMPAD0        | extendedKeyModifier;
const int KeyPress::numberPad1              = VK_NUMPAD1        | extendedKeyModifier;
const int KeyPress::numberPad2              = VK_NUMPAD2        | extendedKeyModifier;
const int KeyPress::numberPad3              = VK_NUMPAD3        | extendedKeyModifier;
const int KeyPress::numberPad4              = VK_NUMPAD4        | extendedKeyModifier;
const int KeyPress::numberPad5              = VK_NUMPAD5        | extendedKeyModifier;
const int KeyPress::numberPad6              = VK_NUMPAD6        | extendedKeyModifier;
const int KeyPress::numberPad7              = VK_NUMPAD7        | extendedKeyModifier;
const int KeyPress::numberPad8              = VK_NUMPAD8        | extendedKeyModifier;
const int KeyPress::numberPad9              = VK_NUMPAD9        | extendedKeyModifier;
const int KeyPress::numberPadAdd            = VK_ADD            | extendedKeyModifier;
const int KeyPress::numberPadSubtract       = VK_SUBTRACT       | extendedKeyModifier;
const int KeyPress::numberPadMultiply       = VK_MULTIPLY       | extendedKeyModifier;
const int KeyPress::numberPadDivide         = VK_DIVIDE         | extendedKeyModifier;
const int KeyPress::numberPadSeparator      = VK_SEPARATOR      | extendedKeyModifier;
const int KeyPress::numberPadDecimalPoint   = VK_DECIMAL        | extendedKeyModifier;
const int KeyPress::numberPadEquals         = 0x92 /*VK_OEM_NEC_EQUAL*/  | extendedKeyModifier;
const int KeyPress::numberPadDelete         = VK_DELETE         | extendedKeyModifier;
const int KeyPress::playKey                 = 0x30000;
const int KeyPress::stopKey                 = 0x30001;
const int KeyPress::fastForwardKey          = 0x30002;
const int KeyPress::rewindKey               = 0x30003;
//==============================================================================
class WindowsBitmapImage  : public ImagePixelData
{
public:
    WindowsBitmapImage (const Image::PixelFormat format,
                        const int w, const int h, const bool clearImage)
        : ImagePixelData (format, w, h)
    {
        jassert (format == Image::RGB || format == Image::ARGB);
        static bool alwaysUse32Bits = isGraphicsCard32Bit(); // NB: for 32-bit cards, it's faster to use a 32-bit image.
        pixelStride = (alwaysUse32Bits || format == Image::ARGB) ? 4 : 3;
        lineStride = -((w * pixelStride + 3) & ~3);
        zerostruct (bitmapInfo);
        bitmapInfo.bV4Size     = sizeof (BITMAPV4HEADER);
        bitmapInfo.bV4Width    = w;
        bitmapInfo.bV4Height   = h;
        bitmapInfo.bV4Planes   = 1;
        bitmapInfo.bV4CSType   = 1;
        bitmapInfo.bV4BitCount = (unsigned short) (pixelStride * 8);
        if (format == Image::ARGB)
        {
            bitmapInfo.bV4AlphaMask      = 0xff000000;
            bitmapInfo.bV4RedMask        = 0xff0000;
            bitmapInfo.bV4GreenMask      = 0xff00;
            bitmapInfo.bV4BlueMask       = 0xff;
            bitmapInfo.bV4V4Compression  = BI_BITFIELDS;
        }
        else
        {
            bitmapInfo.bV4V4Compression  = BI_RGB;
        }
        {
            ScopedDeviceContext deviceContext { nullptr };
            hdc = CreateCompatibleDC (deviceContext.dc);
        }
        SetMapMode (hdc, MM_TEXT);
        hBitmap = CreateDIBSection (hdc, (BITMAPINFO*) &(bitmapInfo), DIB_RGB_COLORS,
                                    (void**) &bitmapData, nullptr, 0);
        if (hBitmap != nullptr)
            previousBitmap = SelectObject (hdc, hBitmap);
        if (format == Image::ARGB && clearImage)
            zeromem (bitmapData, (size_t) std::abs (h * lineStride));
        imageData = bitmapData - (lineStride * (h - 1));
    }
    ~WindowsBitmapImage() override
    {
        SelectObject (hdc, previousBitmap); // Selecting the previous bitmap before deleting the DC avoids a warning in BoundsChecker
        DeleteDC (hdc);
        DeleteObject (hBitmap);
    }
    std::unique_ptr<ImageType> createType() const override    { return std::make_unique<NativeImageType>(); }
    std::unique_ptr<LowLevelGraphicsContext> createLowLevelContext() override
    {
        sendDataChangeMessage();
        return std::make_unique<LowLevelGraphicsSoftwareRenderer> (Image (this));
    }
    void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override
    {
        bitmap.data = imageData + x * pixelStride + y * lineStride;
        bitmap.pixelFormat = pixelFormat;
        bitmap.lineStride = lineStride;
        bitmap.pixelStride = pixelStride;
        if (mode != Image::BitmapData::readOnly)
            sendDataChangeMessage();
    }
    ImagePixelData::Ptr clone() override
    {
        auto im = new WindowsBitmapImage (pixelFormat, width, height, false);
        for (int i = 0; i < height; ++i)
            memcpy (im->imageData + i * lineStride, imageData + i * lineStride, (size_t) lineStride);
        return im;
    }
    void blitToWindow (HWND hwnd, HDC dc, bool transparent, int x, int y, uint8 updateLayeredWindowAlpha) noexcept
    {
        SetMapMode (dc, MM_TEXT);
        if (transparent)
        {
            auto windowBounds = getWindowScreenRect (hwnd);
            POINT p = { -x, -y };
            POINT pos = { windowBounds.left, windowBounds.top };
            SIZE size = { windowBounds.right - windowBounds.left,
                          windowBounds.bottom - windowBounds.top };
            BLENDFUNCTION bf;
            bf.AlphaFormat = 1 /*AC_SRC_ALPHA*/;
            bf.BlendFlags = 0;
            bf.BlendOp = AC_SRC_OVER;
            bf.SourceConstantAlpha = updateLayeredWindowAlpha;
            UpdateLayeredWindow (hwnd, nullptr, &pos, &size, hdc, &p, 0, &bf, 2 /*ULW_ALPHA*/);
        }
        else
        {
            StretchDIBits (dc,
                           x, y, width, height,
                           0, 0, width, height,
                           bitmapData, (const BITMAPINFO*) &bitmapInfo,
                           DIB_RGB_COLORS, SRCCOPY);
        }
    }
    HBITMAP hBitmap;
    HGDIOBJ previousBitmap;
    BITMAPV4HEADER bitmapInfo;
    HDC hdc;
    uint8* bitmapData;
    int pixelStride, lineStride;
    uint8* imageData;
private:
    static bool isGraphicsCard32Bit()
    {
        ScopedDeviceContext deviceContext { nullptr };
        return GetDeviceCaps (deviceContext.dc, BITSPIXEL) > 24;
    }
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsBitmapImage)
};
//==============================================================================
Image createSnapshotOfNativeWindow (void* nativeWindowHandle)
{
    auto hwnd = (HWND) nativeWindowHandle;
    auto r = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (hwnd)), hwnd);
    const auto w = r.getWidth();
    const auto h = r.getHeight();
    auto nativeBitmap = new WindowsBitmapImage (Image::RGB, w, h, true);
    Image bitmap (nativeBitmap);
    ScopedDeviceContext deviceContext { hwnd };
    if (isPerMonitorDPIAwareProcess())
    {
        auto scale = getScaleFactorForWindow (hwnd);
        auto prevStretchMode = SetStretchBltMode (nativeBitmap->hdc, HALFTONE);
        SetBrushOrgEx (nativeBitmap->hdc, 0, 0, nullptr);
        StretchBlt (nativeBitmap->hdc, 0, 0, w, h,
                    deviceContext.dc, 0, 0, roundToInt (w * scale), roundToInt (h * scale),
                    SRCCOPY);
        SetStretchBltMode (nativeBitmap->hdc, prevStretchMode);
    }
    else
    {
        BitBlt (nativeBitmap->hdc, 0, 0, w, h, deviceContext.dc, 0, 0, SRCCOPY);
    }
    return SoftwareImageType().convert (bitmap);
}
//==============================================================================
namespace IconConverters
{
    Image createImageFromHICON (HICON icon)
    {
        if (icon == nullptr)
            return {};
        struct ScopedICONINFO   : public ICONINFO
        {
            ScopedICONINFO()
            {
                hbmColor = nullptr;
                hbmMask = nullptr;
            }
            ~ScopedICONINFO()
            {
                if (hbmColor != nullptr)
                    ::DeleteObject (hbmColor);
                if (hbmMask != nullptr)
                    ::DeleteObject (hbmMask);
            }
        };
        ScopedICONINFO info;
        if (! ::GetIconInfo (icon, &info))
            return {};
        BITMAP bm;
        if (! (::GetObject (info.hbmColor, sizeof (BITMAP), &bm)
               && bm.bmWidth > 0 && bm.bmHeight > 0))
            return {};
        ScopedDeviceContext deviceContext { nullptr };
        if (auto* dc = ::CreateCompatibleDC (deviceContext.dc))
        {
            JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wfour-char-constants")
            BITMAPV5HEADER header = {};
            header.bV5Size = sizeof (BITMAPV5HEADER);
            header.bV5Width = bm.bmWidth;
            header.bV5Height = -bm.bmHeight;
            header.bV5Planes = 1;
            header.bV5Compression = BI_RGB;
            header.bV5BitCount = 32;
            header.bV5RedMask = 0x00FF0000;
            header.bV5GreenMask = 0x0000FF00;
            header.bV5BlueMask = 0x000000FF;
            header.bV5AlphaMask = 0xFF000000;
           #if JUCE_MINGW
            header.bV5CSType = 'Win ';
           #else
            header.bV5CSType = LCS_WINDOWS_COLOR_SPACE;
           #endif
            header.bV5Intent = LCS_GM_IMAGES;
            JUCE_END_IGNORE_WARNINGS_GCC_LIKE
            uint32* bitmapImageData = nullptr;
            if (auto* dib = ::CreateDIBSection (deviceContext.dc, (BITMAPINFO*) &header, DIB_RGB_COLORS,
                                                (void**) &bitmapImageData, nullptr, 0))
            {
                auto oldObject = ::SelectObject (dc, dib);
                auto numPixels = bm.bmWidth * bm.bmHeight;
                auto numColourComponents = (size_t) numPixels * 4;
                // Windows icon data comes as two layers, an XOR mask which contains the bulk
                // of the image data and an AND mask which provides the transparency. Annoyingly
                // the XOR mask can also contain an alpha channel, in which case the transparency
                // mask should not be applied, but there's no way to find out a priori if the XOR
                // mask contains an alpha channel.
                HeapBlock<bool> opacityMask (numPixels);
                memset (bitmapImageData, 0, numColourComponents);
                ::DrawIconEx (dc, 0, 0, icon, bm.bmWidth, bm.bmHeight, 0, nullptr, DI_MASK);
                for (int i = 0; i < numPixels; ++i)
                    opacityMask[i] = (bitmapImageData[i] == 0);
                Image result = Image (Image::ARGB, bm.bmWidth, bm.bmHeight, true);
                Image::BitmapData imageData (result, Image::BitmapData::readWrite);
                memset (bitmapImageData, 0, numColourComponents);
                ::DrawIconEx (dc, 0, 0, icon, bm.bmWidth, bm.bmHeight, 0, nullptr, DI_NORMAL);
                memcpy (imageData.data, bitmapImageData, numColourComponents);
                auto imageHasAlphaChannel = [&imageData, numPixels]()
                {
                    for (int i = 0; i < numPixels; ++i)
                        if (imageData.data[i * 4] != 0)
                            return true;
                    return false;
                };
                if (! imageHasAlphaChannel())
                    for (int i = 0; i < numPixels; ++i)
                        imageData.data[i * 4] = opacityMask[i] ? 0xff : 0x00;
                ::SelectObject (dc, oldObject);
                ::DeleteObject (dib);
                ::DeleteDC (dc);
                return result;
            }
            ::DeleteDC (dc);
        }
        return {};
    }
    HICON createHICONFromImage (const Image& image, const BOOL isIcon, int hotspotX, int hotspotY)
    {
        auto nativeBitmap = new WindowsBitmapImage (Image::ARGB, image.getWidth(), image.getHeight(), true);
        Image bitmap (nativeBitmap);
        {
            Graphics g (bitmap);
            g.drawImageAt (image, 0, 0);
        }
        auto mask = CreateBitmap (image.getWidth(), image.getHeight(), 1, 1, nullptr);
        ICONINFO info;
        info.fIcon = isIcon;
        info.xHotspot = (DWORD) hotspotX;
        info.yHotspot = (DWORD) hotspotY;
        info.hbmMask = mask;
        info.hbmColor = nativeBitmap->hBitmap;
        auto hi = CreateIconIndirect (&info);
        DeleteObject (mask);
        return hi;
    }
}
//==============================================================================
JUCE_IUNKNOWNCLASS (ITipInvocation, "37c994e7-432b-4834-a2f7-dce1f13b834b")
{
    static CLSID getCLSID() noexcept   { return { 0x4ce576fa, 0x83dc, 0x4f88, { 0x95, 0x1c, 0x9d, 0x07, 0x82, 0xb4, 0xe3, 0x76 } }; }
    JUCE_COMCALL Toggle (HWND) = 0;
};
struct OnScreenKeyboard   : public DeletedAtShutdown,
                            private Timer
{
    void activate()
    {
        shouldBeActive = true;
        startTimer (10);
    }
    void deactivate()
    {
        shouldBeActive = false;
        startTimer (10);
    }
    JUCE_DECLARE_SINGLETON_SINGLETHREADED (OnScreenKeyboard, false)
private:
    OnScreenKeyboard()
    {
        tipInvocation.CoCreateInstance (ITipInvocation::getCLSID(), CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER);
    }
    ~OnScreenKeyboard() override
    {
        clearSingletonInstance();
    }
    void timerCallback() override
    {
        stopTimer();
        if (reentrant || tipInvocation == nullptr)
            return;
        const ScopedValueSetter<bool> setter (reentrant, true, false);
        auto isActive = isKeyboardVisible();
        if (isActive != shouldBeActive)
        {
            if (! isActive)
            {
                tipInvocation->Toggle (GetDesktopWindow());
            }
            else
            {
                if (auto hwnd = FindWindow (L"IPTip_Main_Window", nullptr))
                    PostMessage (hwnd, WM_SYSCOMMAND, (int) SC_CLOSE, 0);
            }
        }
    }
    bool isVisible()
    {
        if (auto hwnd = FindWindowEx (nullptr, nullptr, L"ApplicationFrameWindow", nullptr))
            return FindWindowEx (hwnd, nullptr, L"Windows.UI.Core.CoreWindow", L"Microsoft Text Input Application") != nullptr;
        return false;
    }
    bool isVisibleLegacy()
    {
        if (auto hwnd = FindWindow (L"IPTip_Main_Window", nullptr))
        {
            auto style = GetWindowLong (hwnd, GWL_STYLE);
            return (style & WS_DISABLED) == 0 && (style & WS_VISIBLE) != 0;
        }
        return false;
    }
    bool isKeyboardVisible()
    {
        if (isVisible())
            return true;
        // isVisible() may fail on Win10 versions < 1709 so try the old method too
        return isVisibleLegacy();
    }
    bool shouldBeActive = false, reentrant = false;
    ComSmartPtr<ITipInvocation> tipInvocation;
};
JUCE_IMPLEMENT_SINGLETON (OnScreenKeyboard)
//==============================================================================
struct HSTRING_PRIVATE;
typedef HSTRING_PRIVATE* HSTRING;
struct IInspectable : public IUnknown
{
    JUCE_COMCALL GetIids (ULONG* ,IID**) = 0;
    JUCE_COMCALL GetRuntimeClassName (HSTRING*) = 0;
    JUCE_COMCALL GetTrustLevel (void*) = 0;
};
JUCE_COMCLASS (IUIViewSettingsInterop, "3694dbf9-8f68-44be-8ff5-195c98ede8a6")  : public IInspectable
{
    JUCE_COMCALL GetForWindow (HWND, REFIID, void**) = 0;
};
JUCE_COMCLASS (IUIViewSettings, "c63657f6-8850-470d-88f8-455e16ea2c26")  : public IInspectable
{
    enum UserInteractionMode
    {
        Mouse = 0,
        Touch = 1
    };
    JUCE_COMCALL GetUserInteractionMode (UserInteractionMode*) = 0;
};
struct UWPUIViewSettings
{
    UWPUIViewSettings()
    {
        ComBaseModule dll (L"api-ms-win-core-winrt-l1-1-0");
        if (dll.h != nullptr)
        {
            roInitialize           = (RoInitializeFuncPtr)           ::GetProcAddress (dll.h, "RoInitialize");
            roGetActivationFactory = (RoGetActivationFactoryFuncPtr) ::GetProcAddress (dll.h, "RoGetActivationFactory");
            createHString          = (WindowsCreateStringFuncPtr)    ::GetProcAddress (dll.h, "WindowsCreateString");
            deleteHString          = (WindowsDeleteStringFuncPtr)    ::GetProcAddress (dll.h, "WindowsDeleteString");
            if (roInitialize == nullptr || roGetActivationFactory == nullptr
                 || createHString == nullptr || deleteHString == nullptr)
                return;
            auto status = roInitialize (1);
            if (status != S_OK && status != S_FALSE && (unsigned) status != 0x80010106L)
                return;
            LPCWSTR uwpClassName = L"Windows.UI.ViewManagement.UIViewSettings";
            HSTRING uwpClassId;
            if (createHString (uwpClassName, (::UINT32) wcslen (uwpClassName), &uwpClassId) != S_OK
                 || uwpClassId == nullptr)
                return;
            JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
            status = roGetActivationFactory (uwpClassId, __uuidof (IUIViewSettingsInterop),
                                             (void**) viewSettingsInterop.resetAndGetPointerAddress());
            JUCE_END_IGNORE_WARNINGS_GCC_LIKE
            deleteHString (uwpClassId);
            if (status != S_OK || viewSettingsInterop == nullptr)
                return;
            // move dll into member var
            comBaseDLL = std::move (dll);
        }
    }
    bool isTabletModeActivatedForWindow (::HWND hWnd) const
    {
        if (viewSettingsInterop == nullptr)
            return false;
        ComSmartPtr<IUIViewSettings> viewSettings;
        JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
        if (viewSettingsInterop->GetForWindow (hWnd, __uuidof (IUIViewSettings),
                                               (void**) viewSettings.resetAndGetPointerAddress()) == S_OK
             && viewSettings != nullptr)
        {
            IUIViewSettings::UserInteractionMode mode;
            if (viewSettings->GetUserInteractionMode (&mode) == S_OK)
                return mode == IUIViewSettings::Touch;
        }
        JUCE_END_IGNORE_WARNINGS_GCC_LIKE
        return false;
    }
private:
    //==============================================================================
    struct ComBaseModule
    {
        ComBaseModule() {}
        ComBaseModule (LPCWSTR libraryName) : h (::LoadLibrary (libraryName)) {}
        ComBaseModule (ComBaseModule&& o) : h (o.h) { o.h = nullptr; }
        ~ComBaseModule() { release(); }
        void release() { if (h != nullptr) ::FreeLibrary (h); h = nullptr; }
        ComBaseModule& operator= (ComBaseModule&& o) { release(); h = o.h; o.h = nullptr; return *this; }
        HMODULE h = {};
    };
    using RoInitializeFuncPtr           = HRESULT (WINAPI*) (int);
    using RoGetActivationFactoryFuncPtr = HRESULT (WINAPI*) (HSTRING, REFIID, void**);
    using WindowsCreateStringFuncPtr    = HRESULT (WINAPI*) (LPCWSTR,UINT32, HSTRING*);
    using WindowsDeleteStringFuncPtr    = HRESULT (WINAPI*) (HSTRING);
    ComBaseModule comBaseDLL;
    ComSmartPtr<IUIViewSettingsInterop> viewSettingsInterop;
    RoInitializeFuncPtr roInitialize;
    RoGetActivationFactoryFuncPtr roGetActivationFactory;
    WindowsCreateStringFuncPtr createHString;
    WindowsDeleteStringFuncPtr deleteHString;
};
//==============================================================================
class HWNDComponentPeer  : public ComponentPeer,
                           private Timer
                          #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client
                           , public ModifierKeyReceiver
                          #endif
{
public:
    enum RenderingEngineType
    {
        softwareRenderingEngine = 0,
        direct2DRenderingEngine
    };
    //==============================================================================
    HWNDComponentPeer (Component& comp, int windowStyleFlags, HWND parent, bool nonRepainting)
        : ComponentPeer (comp, windowStyleFlags),
          dontRepaint (nonRepainting),
          parentToAddTo (parent),
          currentRenderingEngine (softwareRenderingEngine)
    {
        callFunctionIfNotLocked (&createWindowCallback, this);
        setTitle (component.getName());
        updateShadower();
        OnScreenKeyboard::getInstance();
        getNativeRealtimeModifiers = []
        {
            HWNDComponentPeer::updateKeyModifiers();
            int mouseMods = 0;
            if (HWNDComponentPeer::isKeyDown (VK_LBUTTON))  mouseMods |= ModifierKeys::leftButtonModifier;
            if (HWNDComponentPeer::isKeyDown (VK_RBUTTON))  mouseMods |= ModifierKeys::rightButtonModifier;
            if (HWNDComponentPeer::isKeyDown (VK_MBUTTON))  mouseMods |= ModifierKeys::middleButtonModifier;
            ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons().withFlags (mouseMods);
            return ModifierKeys::currentModifiers;
        };
    }
    ~HWNDComponentPeer() override
    {
        // do this first to avoid messages arriving for this window before it's destroyed
        JuceWindowIdentifier::setAsJUCEWindow (hwnd, false);
        if (isAccessibilityActive)
            WindowsAccessibility::revokeUIAMapEntriesForWindow (hwnd);
        shadower = nullptr;
        currentTouches.deleteAllTouchesForPeer (this);
        callFunctionIfNotLocked (&destroyWindowCallback, (void*) hwnd);
        if (currentWindowIcon != nullptr)
            DestroyIcon (currentWindowIcon);
        if (dropTarget != nullptr)
        {
            dropTarget->peerIsDeleted = true;
            dropTarget->Release();
            dropTarget = nullptr;
        }
       #if JUCE_DIRECT2D
        direct2DContext = nullptr;
       #endif
    }
    //==============================================================================
    void* getNativeHandle() const override    { return hwnd; }
    void setVisible (bool shouldBeVisible) override
    {
        const ScopedValueSetter<bool> scope (shouldIgnoreModalDismiss, true);
        ShowWindow (hwnd, shouldBeVisible ? SW_SHOWNA : SW_HIDE);
        if (shouldBeVisible)
            InvalidateRect (hwnd, nullptr, 0);
        else
            lastPaintTime = 0;
    }
    void setTitle (const String& title) override
    {
        // Unfortunately some ancient bits of win32 mean you can only perform this operation from the message thread.
        JUCE_ASSERT_MESSAGE_THREAD
        SetWindowText (hwnd, title.toWideCharPointer());
    }
    void repaintNowIfTransparent()
    {
        if (isUsingUpdateLayeredWindow() && lastPaintTime > 0 && Time::getMillisecondCounter() > lastPaintTime + 30)
            handlePaintMessage();
    }
    void updateBorderSize()
    {
        WINDOWINFO info;
        info.cbSize = sizeof (info);
        if (GetWindowInfo (hwnd, &info))
            windowBorder = BorderSize<int> (roundToInt ((info.rcClient.top    - info.rcWindow.top)    / scaleFactor),
                                            roundToInt ((info.rcClient.left   - info.rcWindow.left)   / scaleFactor),
                                            roundToInt ((info.rcWindow.bottom - info.rcClient.bottom) / scaleFactor),
                                            roundToInt ((info.rcWindow.right  - info.rcClient.right)  / scaleFactor));
       #if JUCE_DIRECT2D
        if (direct2DContext != nullptr)
            direct2DContext->resized();
       #endif
    }
    void setBounds (const Rectangle<int>& bounds, bool isNowFullScreen) override
    {
        const ScopedValueSetter<bool> scope (shouldIgnoreModalDismiss, true);
        fullScreen = isNowFullScreen;
        auto newBounds = windowBorder.addedTo (bounds);
        if (isUsingUpdateLayeredWindow())
        {
            if (auto parentHwnd = GetParent (hwnd))
            {
                auto parentRect = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (parentHwnd)), hwnd);
                newBounds.translate (parentRect.getX(), parentRect.getY());
            }
        }
        auto oldBounds = getBounds();
        const bool hasMoved = (oldBounds.getPosition() != bounds.getPosition());
        const bool hasResized = (oldBounds.getWidth() != bounds.getWidth()
                                  || oldBounds.getHeight() != bounds.getHeight());
        DWORD flags = SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER;
        if (! hasMoved)    flags |= SWP_NOMOVE;
        if (! hasResized)  flags |= SWP_NOSIZE;
        setWindowPos (hwnd, newBounds, flags, numInDpiChange == 0);
        if (hasResized && isValidPeer (this))
        {
            updateBorderSize();
            repaintNowIfTransparent();
        }
    }
    Rectangle<int> getBounds() const override
    {
        auto bounds = [this]
        {
            if (parentToAddTo == nullptr)
                return convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (hwnd)), hwnd);
            auto localBounds = rectangleFromRECT (getWindowClientRect (hwnd));
            if (isPerMonitorDPIAwareWindow (hwnd))
                return (localBounds.toDouble() / getPlatformScaleFactor()).toNearestInt();
            return localBounds;
        }();
        return windowBorder.subtractedFrom (bounds);
    }
    Point<int> getScreenPosition() const
    {
        auto r = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (hwnd)), hwnd);
        return { r.getX() + windowBorder.getLeft(),
                 r.getY() + windowBorder.getTop() };
    }
    Point<float> localToGlobal (Point<float> relativePosition) override  { return relativePosition + getScreenPosition().toFloat(); }
    Point<float> globalToLocal (Point<float> screenPosition) override    { return screenPosition   - getScreenPosition().toFloat(); }
    using ComponentPeer::localToGlobal;
    using ComponentPeer::globalToLocal;
    void setAlpha (float newAlpha) override
    {
        const ScopedValueSetter<bool> scope (shouldIgnoreModalDismiss, true);
        auto intAlpha = (uint8) jlimit (0, 255, (int) (newAlpha * 255.0f));
        if (component.isOpaque())
        {
            if (newAlpha < 1.0f)
            {
                SetWindowLong (hwnd, GWL_EXSTYLE, GetWindowLong (hwnd, GWL_EXSTYLE) | WS_EX_LAYERED);
                SetLayeredWindowAttributes (hwnd, RGB (0, 0, 0), intAlpha, LWA_ALPHA);
            }
            else
            {
                SetWindowLong (hwnd, GWL_EXSTYLE, GetWindowLong (hwnd, GWL_EXSTYLE) & ~WS_EX_LAYERED);
                RedrawWindow (hwnd, nullptr, nullptr, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN);
            }
        }
        else
        {
            updateLayeredWindowAlpha = intAlpha;
            component.repaint();
        }
    }
    void setMinimised (bool shouldBeMinimised) override
    {
        const ScopedValueSetter<bool> scope (shouldIgnoreModalDismiss, true);
        if (shouldBeMinimised != isMinimised())
            ShowWindow (hwnd, shouldBeMinimised ? SW_MINIMIZE : SW_RESTORE);
    }
    bool isMinimised() const override
    {
        WINDOWPLACEMENT wp;
        wp.length = sizeof (WINDOWPLACEMENT);
        GetWindowPlacement (hwnd, &wp);
        return wp.showCmd == SW_SHOWMINIMIZED;
    }
    void setFullScreen (bool shouldBeFullScreen) override
    {
        const ScopedValueSetter<bool> scope (shouldIgnoreModalDismiss, true);
        setMinimised (false);
        if (isFullScreen() != shouldBeFullScreen)
        {
            if (constrainer != nullptr)
                constrainer->resizeStart();
            fullScreen = shouldBeFullScreen;
            const WeakReference<Component> deletionChecker (&component);
            if (! fullScreen)
            {
                auto boundsCopy = lastNonFullscreenBounds;
                if (hasTitleBar())
                    ShowWindow (hwnd, SW_SHOWNORMAL);
                if (! boundsCopy.isEmpty())
                    setBounds (ScalingHelpers::scaledScreenPosToUnscaled (component, boundsCopy), false);
            }
            else
            {
                if (hasTitleBar())
                    ShowWindow (hwnd, SW_SHOWMAXIMIZED);
                else
                    SendMessageW (hwnd, WM_SETTINGCHANGE, 0, 0);
            }
            if (deletionChecker != nullptr)
                handleMovedOrResized();
            if (constrainer != nullptr)
                constrainer->resizeEnd();
        }
    }
    bool isFullScreen() const override
    {
        if (! hasTitleBar())
            return fullScreen;
        WINDOWPLACEMENT wp;
        wp.length = sizeof (wp);
        GetWindowPlacement (hwnd, &wp);
        return wp.showCmd == SW_SHOWMAXIMIZED;
    }
    bool contains (Point<int> localPos, bool trueIfInAChildWindow) const override
    {
        auto r = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (hwnd)), hwnd);
        if (! r.withZeroOrigin().contains (localPos))
            return false;
        auto w = WindowFromPoint (POINTFromPoint (convertLogicalScreenPointToPhysical (localPos + getScreenPosition(),
                                                                                       hwnd)));
        return w == hwnd || (trueIfInAChildWindow && (IsChild (hwnd, w) != 0));
    }
    BorderSize<int> getFrameSize() const override
    {
        return windowBorder;
    }
    bool setAlwaysOnTop (bool alwaysOnTop) override
    {
        const bool oldDeactivate = shouldDeactivateTitleBar;
        shouldDeactivateTitleBar = ((styleFlags & windowIsTemporary) == 0);
        setWindowZOrder (hwnd, alwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST);
        shouldDeactivateTitleBar = oldDeactivate;
        if (shadower != nullptr)
            handleBroughtToFront();
        return true;
    }
    void toFront (bool makeActive) override
    {
        const ScopedValueSetter<bool> scope (shouldIgnoreModalDismiss, true);
        setMinimised (false);
        const bool oldDeactivate = shouldDeactivateTitleBar;
        shouldDeactivateTitleBar = ((styleFlags & windowIsTemporary) == 0);
        callFunctionIfNotLocked (makeActive ? &toFrontCallback1 : &toFrontCallback2, hwnd);
        shouldDeactivateTitleBar = oldDeactivate;
        if (! makeActive)
        {
            // in this case a broughttofront call won't have occurred, so do it now..
            handleBroughtToFront();
        }
    }
    void toBehind (ComponentPeer* other) override
    {
        const ScopedValueSetter<bool> scope (shouldIgnoreModalDismiss, true);
        if (auto* otherPeer = dynamic_cast<HWNDComponentPeer*> (other))
        {
            setMinimised (false);
            // Must be careful not to try to put a topmost window behind a normal one, or Windows
            // promotes the normal one to be topmost!
            if (component.isAlwaysOnTop() == otherPeer->getComponent().isAlwaysOnTop())
                setWindowZOrder (hwnd, otherPeer->hwnd);
            else if (otherPeer->getComponent().isAlwaysOnTop())
                setWindowZOrder (hwnd, HWND_TOP);
        }
        else
        {
            jassertfalse; // wrong type of window?
        }
    }
    bool isFocused() const override
    {
        return callFunctionIfNotLocked (&getFocusCallback, nullptr) == (void*) hwnd;
    }
    void grabFocus() override
    {
        const ScopedValueSetter<bool> scope (shouldIgnoreModalDismiss, true);
        const bool oldDeactivate = shouldDeactivateTitleBar;
        shouldDeactivateTitleBar = ((styleFlags & windowIsTemporary) == 0);
        callFunctionIfNotLocked (&setFocusCallback, hwnd);
        shouldDeactivateTitleBar = oldDeactivate;
    }
    void textInputRequired (Point<int>, TextInputTarget&) override
    {
        if (! hasCreatedCaret)
        {
            hasCreatedCaret = true;
            CreateCaret (hwnd, (HBITMAP) 1, 0, 0);
        }
        ShowCaret (hwnd);
        SetCaretPos (0, 0);
        if (uwpViewSettings.isTabletModeActivatedForWindow (hwnd))
            OnScreenKeyboard::getInstance()->activate();
    }
    void dismissPendingTextInput() override
    {
        imeHandler.handleSetContext (hwnd, false);
        if (uwpViewSettings.isTabletModeActivatedForWindow (hwnd))
            OnScreenKeyboard::getInstance()->deactivate();
    }
    void repaint (const Rectangle<int>& area) override
    {
        auto r = RECTFromRectangle ((area.toDouble() * getPlatformScaleFactor()).getSmallestIntegerContainer());
        InvalidateRect (hwnd, &r, FALSE);
    }
    void performAnyPendingRepaintsNow() override
    {
        if (component.isVisible())
        {
            WeakReference<Component> localRef (&component);
            MSG m;
            if (isUsingUpdateLayeredWindow() || PeekMessage (&m, hwnd, WM_PAINT, WM_PAINT, PM_REMOVE))
                if (localRef != nullptr) // (the PeekMessage call can dispatch messages, which may delete this comp)
                    handlePaintMessage();
        }
    }
    //==============================================================================
    static HWNDComponentPeer* getOwnerOfWindow (HWND h) noexcept
    {
        if (h != nullptr && JuceWindowIdentifier::isJUCEWindow (h))
            return (HWNDComponentPeer*) GetWindowLongPtr (h, 8);
        return nullptr;
    }
    //==============================================================================
    bool isInside (HWND h) const noexcept
    {
        return GetAncestor (hwnd, GA_ROOT) == h;
    }
    //==============================================================================
    static bool isKeyDown (const int key) noexcept  { return (GetAsyncKeyState (key) & 0x8000) != 0; }
    static void updateKeyModifiers() noexcept
    {
        int keyMods = 0;
        if (isKeyDown (VK_SHIFT))   keyMods |= ModifierKeys::shiftModifier;
        if (isKeyDown (VK_CONTROL)) keyMods |= ModifierKeys::ctrlModifier;
        if (isKeyDown (VK_MENU))    keyMods |= ModifierKeys::altModifier;
        // workaround: Windows maps AltGr to left-Ctrl + right-Alt.
        if (isKeyDown (VK_RMENU) && !isKeyDown (VK_RCONTROL))
        {
            keyMods = (keyMods & ~ModifierKeys::ctrlModifier) | ModifierKeys::altModifier;
        }
        ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withOnlyMouseButtons().withFlags (keyMods);
    }
    static void updateModifiersFromWParam (const WPARAM wParam)
    {
        int mouseMods = 0;
        if (wParam & MK_LBUTTON)   mouseMods |= ModifierKeys::leftButtonModifier;
        if (wParam & MK_RBUTTON)   mouseMods |= ModifierKeys::rightButtonModifier;
        if (wParam & MK_MBUTTON)   mouseMods |= ModifierKeys::middleButtonModifier;
        ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons().withFlags (mouseMods);
        updateKeyModifiers();
    }
    //==============================================================================
    bool dontRepaint;
    static ModifierKeys modifiersAtLastCallback;
    //==============================================================================
    struct FileDropTarget    : public ComBaseClassHelper<IDropTarget>
    {
        FileDropTarget (HWNDComponentPeer& p)   : peer (p) {}
        JUCE_COMRESULT DragEnter (IDataObject* pDataObject, DWORD grfKeyState, POINTL mousePos, DWORD* pdwEffect) override
        {
            auto hr = updateFileList (pDataObject);
            if (FAILED (hr))
                return hr;
            return DragOver (grfKeyState, mousePos, pdwEffect);
        }
        JUCE_COMRESULT DragLeave() override
        {
            if (peerIsDeleted)
                return S_FALSE;
            peer.handleDragExit (dragInfo);
            return S_OK;
        }
        JUCE_COMRESULT DragOver (DWORD /*grfKeyState*/, POINTL mousePos, DWORD* pdwEffect) override
        {
            if (peerIsDeleted)
                return S_FALSE;
            dragInfo.position = getMousePos (mousePos).roundToInt();
            *pdwEffect = peer.handleDragMove (dragInfo) ? (DWORD) DROPEFFECT_COPY
                                                        : (DWORD) DROPEFFECT_NONE;
            return S_OK;
        }
        JUCE_COMRESULT Drop (IDataObject* pDataObject, DWORD /*grfKeyState*/, POINTL mousePos, DWORD* pdwEffect) override
        {
            auto hr = updateFileList (pDataObject);
            if (FAILED (hr))
                return hr;
            dragInfo.position = getMousePos (mousePos).roundToInt();
            *pdwEffect = peer.handleDragDrop (dragInfo) ? (DWORD) DROPEFFECT_COPY
                                                        : (DWORD) DROPEFFECT_NONE;
            return S_OK;
        }
        HWNDComponentPeer& peer;
        ComponentPeer::DragInfo dragInfo;
        bool peerIsDeleted = false;
    private:
        Point<float> getMousePos (POINTL mousePos) const
        {
            return peer.getComponent().getLocalPoint (nullptr, convertPhysicalScreenPointToLogical (pointFromPOINT ({ mousePos.x, mousePos.y }),
                                                                                                    (HWND) peer.getNativeHandle()).toFloat());
        }
        struct DroppedData
        {
            DroppedData (IDataObject* dataObject, CLIPFORMAT type)
            {
                FORMATETC format = { type, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
                if (SUCCEEDED (error = dataObject->GetData (&format, &medium)) && medium.hGlobal != nullptr)
                {
                    dataSize = GlobalSize (medium.hGlobal);
                    data = GlobalLock (medium.hGlobal);
                }
            }
            ~DroppedData()
            {
                if (data != nullptr && medium.hGlobal != nullptr)
                    GlobalUnlock (medium.hGlobal);
            }
            HRESULT error;
            STGMEDIUM medium { TYMED_HGLOBAL, { nullptr }, nullptr };
            void* data = {};
            SIZE_T dataSize;
        };
        void parseFileList (HDROP dropFiles)
        {
            dragInfo.files.clearQuick();
            std::vector<TCHAR> nameBuffer;
            const auto numFiles = DragQueryFile (dropFiles, ~(UINT) 0, nullptr, 0);
            for (UINT i = 0; i < numFiles; ++i)
            {
                const auto bufferSize = DragQueryFile (dropFiles, i, nullptr, 0);
                nameBuffer.clear();
                nameBuffer.resize (bufferSize + 1, 0); // + 1 for the null terminator
                const auto readCharacters = DragQueryFile (dropFiles, i, nameBuffer.data(), (UINT) nameBuffer.size());
                ignoreUnused (readCharacters);
                jassert (readCharacters == bufferSize);
                dragInfo.files.add (String (nameBuffer.data()));
            }
        }
        HRESULT updateFileList (IDataObject* const dataObject)
        {
            if (peerIsDeleted)
                return S_FALSE;
            dragInfo.clear();
            {
                DroppedData fileData (dataObject, CF_HDROP);
                if (SUCCEEDED (fileData.error))
                {
                    parseFileList (static_cast<HDROP> (fileData.data));
                    return S_OK;
                }
            }
            DroppedData textData (dataObject, CF_UNICODETEXT);
            if (SUCCEEDED (textData.error))
            {
                dragInfo.text = String (CharPointer_UTF16 ((const WCHAR*) textData.data),
                                        CharPointer_UTF16 ((const WCHAR*) addBytesToPointer (textData.data, textData.dataSize)));
                return S_OK;
            }
            return textData.error;
        }
        JUCE_DECLARE_NON_COPYABLE (FileDropTarget)
    };
    static bool offerKeyMessageToJUCEWindow (MSG& m)
    {
        if (m.message == WM_KEYDOWN || m.message == WM_KEYUP)
        {
            if (Component::getCurrentlyFocusedComponent() != nullptr)
            {
                if (auto* peer = getOwnerOfWindow (m.hwnd))
                {
                    ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { m.hwnd };
                    return m.message == WM_KEYDOWN ? peer->doKeyDown (m.wParam)
                                                   : peer->doKeyUp (m.wParam);
                }
            }
        }
        return false;
    }
    double getPlatformScaleFactor() const noexcept override
    {
       #if ! JUCE_WIN_PER_MONITOR_DPI_AWARE
        return 1.0;
       #else
        if (! isPerMonitorDPIAwareWindow (hwnd))
            return 1.0;
        if (auto* parentHWND = GetParent (hwnd))
        {
            if (auto* parentPeer = getOwnerOfWindow (parentHWND))
                return parentPeer->getPlatformScaleFactor();
            if (getDPIForWindow != nullptr)
                return getScaleFactorForWindow (parentHWND);
        }
        return scaleFactor;
       #endif
    }
private:
    HWND hwnd, parentToAddTo;
    std::unique_ptr<DropShadower> shadower;
    RenderingEngineType currentRenderingEngine;
   #if JUCE_DIRECT2D
    std::unique_ptr<Direct2DLowLevelGraphicsContext> direct2DContext;
   #endif
    uint32 lastPaintTime = 0;
    ULONGLONG lastMagnifySize = 0;
    bool fullScreen = false, isDragging = false, isMouseOver = false,
         hasCreatedCaret = false, constrainerIsResizing = false;
    BorderSize<int> windowBorder;
    HICON currentWindowIcon = nullptr;
    FileDropTarget* dropTarget = nullptr;
    uint8 updateLayeredWindowAlpha = 255;
    UWPUIViewSettings uwpViewSettings;
   #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client
    ModifierKeyProvider* modProvider = nullptr;
   #endif
    double scaleFactor = 1.0;
    int numInDpiChange = 0;
    bool isAccessibilityActive = false;
    //==============================================================================
    static MultiTouchMapper<DWORD> currentTouches;
    //==============================================================================
    struct TemporaryImage    : private Timer
    {
        TemporaryImage() {}
        Image& getImage (bool transparent, int w, int h)
        {
            auto format = transparent ? Image::ARGB : Image::RGB;
            if ((! image.isValid()) || image.getWidth() < w || image.getHeight() < h || image.getFormat() != format)
                image = Image (new WindowsBitmapImage (format, (w + 31) & ~31, (h + 31) & ~31, false));
            startTimer (3000);
            return image;
        }
        void timerCallback() override
        {
            stopTimer();
            image = {};
        }
    private:
        Image image;
        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TemporaryImage)
    };
    TemporaryImage offscreenImageGenerator;
    //==============================================================================
    class WindowClassHolder    : private DeletedAtShutdown
    {
    public:
        WindowClassHolder()
        {
            // this name has to be different for each app/dll instance because otherwise poor old Windows can
            // get a bit confused (even despite it not being a process-global window class).
            String windowClassName ("JUCE_");
            windowClassName << String::toHexString (Time::currentTimeMillis());
            auto moduleHandle = (HINSTANCE) Process::getCurrentModuleInstanceHandle();
            TCHAR moduleFile[1024] = {};
            GetModuleFileName (moduleHandle, moduleFile, 1024);
            WORD iconNum = 0;
            WNDCLASSEX wcex = {};
            wcex.cbSize         = sizeof (wcex);
            wcex.style          = CS_OWNDC;
            wcex.lpfnWndProc    = (WNDPROC) windowProc;
            wcex.lpszClassName  = windowClassName.toWideCharPointer();
            wcex.cbWndExtra     = 32;
            wcex.hInstance      = moduleHandle;
            wcex.hIcon          = ExtractAssociatedIcon (moduleHandle, moduleFile, &iconNum);
            iconNum = 1;
            wcex.hIconSm        = ExtractAssociatedIcon (moduleHandle, moduleFile, &iconNum);
            atom = RegisterClassEx (&wcex);
            jassert (atom != 0);
            isEventBlockedByModalComps = checkEventBlockedByModalComps;
        }
        ~WindowClassHolder()
        {
            if (ComponentPeer::getNumPeers() == 0)
                UnregisterClass (getWindowClassName(), (HINSTANCE) Process::getCurrentModuleInstanceHandle());
            clearSingletonInstance();
        }
        LPCTSTR getWindowClassName() const noexcept     { return (LPCTSTR) (pointer_sized_uint) atom; }
        JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (WindowClassHolder)
    private:
        ATOM atom;
        static bool isHWNDBlockedByModalComponents (HWND h)
        {
            for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
                if (auto* c = Desktop::getInstance().getComponent (i))
                    if ((! c->isCurrentlyBlockedByAnotherModalComponent())
                          && IsChild ((HWND) c->getWindowHandle(), h))
                        return false;
            return true;
        }
        static bool checkEventBlockedByModalComps (const MSG& m)
        {
            if (Component::getNumCurrentlyModalComponents() == 0 || JuceWindowIdentifier::isJUCEWindow (m.hwnd))
                return false;
            switch (m.message)
            {
                case WM_MOUSEMOVE:
                case WM_NCMOUSEMOVE:
                case 0x020A: /* WM_MOUSEWHEEL */
                case 0x020E: /* WM_MOUSEHWHEEL */
                case WM_KEYUP:
                case WM_SYSKEYUP:
                case WM_CHAR:
                case WM_APPCOMMAND:
                case WM_LBUTTONUP:
                case WM_MBUTTONUP:
                case WM_RBUTTONUP:
                case WM_MOUSEACTIVATE:
                case WM_NCMOUSEHOVER:
                case WM_MOUSEHOVER:
                case WM_TOUCH:
                case WM_POINTERUPDATE:
                case WM_NCPOINTERUPDATE:
                case WM_POINTERWHEEL:
                case WM_POINTERHWHEEL:
                case WM_POINTERUP:
                case WM_POINTERACTIVATE:
                    return isHWNDBlockedByModalComponents(m.hwnd);
                case WM_NCLBUTTONDOWN:
                case WM_NCLBUTTONDBLCLK:
                case WM_NCRBUTTONDOWN:
                case WM_NCRBUTTONDBLCLK:
                case WM_NCMBUTTONDOWN:
                case WM_NCMBUTTONDBLCLK:
                case WM_LBUTTONDOWN:
                case WM_LBUTTONDBLCLK:
                case WM_MBUTTONDOWN:
                case WM_MBUTTONDBLCLK:
                case WM_RBUTTONDOWN:
                case WM_RBUTTONDBLCLK:
                case WM_KEYDOWN:
                case WM_SYSKEYDOWN:
                case WM_NCPOINTERDOWN:
                case WM_POINTERDOWN:
                    if (isHWNDBlockedByModalComponents (m.hwnd))
                    {
                        if (auto* modal = Component::getCurrentlyModalComponent (0))
                            modal->inputAttemptWhenModal();
                        return true;
                    }
                    break;
                default:
                    break;
            }
            return false;
        }
        JUCE_DECLARE_NON_COPYABLE (WindowClassHolder)
    };
    //==============================================================================
    static void* createWindowCallback (void* userData)
    {
        static_cast<HWNDComponentPeer*> (userData)->createWindow();
        return nullptr;
    }
    void createWindow()
    {
        DWORD exstyle = 0;
        DWORD type = WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
        if (hasTitleBar())
        {
            type |= WS_OVERLAPPED;
            if ((styleFlags & windowHasCloseButton) != 0)
            {
                type |= WS_SYSMENU;
            }
            else
            {
                // annoyingly, windows won't let you have a min/max button without a close button
                jassert ((styleFlags & (windowHasMinimiseButton | windowHasMaximiseButton)) == 0);
            }
            if ((styleFlags & windowIsResizable) != 0)
                type |= WS_THICKFRAME;
        }
        else if (parentToAddTo != nullptr)
        {
            type |= WS_CHILD;
        }
        else
        {
            type |= WS_POPUP | WS_SYSMENU;
        }
        if ((styleFlags & windowAppearsOnTaskbar) == 0)
            exstyle |= WS_EX_TOOLWINDOW;
        else
            exstyle |= WS_EX_APPWINDOW;
        if ((styleFlags & windowHasMinimiseButton) != 0)    type |= WS_MINIMIZEBOX;
        if ((styleFlags & windowHasMaximiseButton) != 0)    type |= WS_MAXIMIZEBOX;
        if ((styleFlags & windowIgnoresMouseClicks) != 0)   exstyle |= WS_EX_TRANSPARENT;
        if ((styleFlags & windowIsSemiTransparent) != 0)    exstyle |= WS_EX_LAYERED;
        hwnd = CreateWindowEx (exstyle, WindowClassHolder::getInstance()->getWindowClassName(),
                               L"", type, 0, 0, 0, 0, parentToAddTo, nullptr,
                               (HINSTANCE) Process::getCurrentModuleInstanceHandle(), nullptr);
       #if JUCE_DEBUG
        // The DPI-awareness context of this window and JUCE's hidden message window are different.
        // You normally want these to match otherwise timer events and async messages will happen
        // in a different context to normal HWND messages which can cause issues with UI scaling.
        jassert (isPerMonitorDPIAwareWindow (hwnd) == isPerMonitorDPIAwareWindow (juce_messageWindowHandle)
                   || isInScopedDPIAwarenessDisabler());
       #endif
        if (hwnd != nullptr)
        {
            SetWindowLongPtr (hwnd, 0, 0);
            SetWindowLongPtr (hwnd, 8, (LONG_PTR) this);
            JuceWindowIdentifier::setAsJUCEWindow (hwnd, true);
            if (dropTarget == nullptr)
            {
                HWNDComponentPeer* peer = nullptr;
                if (dontRepaint)
                    peer = getOwnerOfWindow (parentToAddTo);
                if (peer == nullptr)
                    peer = this;
                dropTarget = new FileDropTarget (*peer);
            }
            RegisterDragDrop (hwnd, dropTarget);
            if (canUseMultiTouch())
                registerTouchWindow (hwnd, 0);
            setDPIAwareness();
            if (isPerMonitorDPIAwareThread())
                scaleFactor = getScaleFactorForWindow (hwnd);
            setMessageFilter();
            updateBorderSize();
            checkForPointerAPI();
            // This is needed so that our plugin window gets notified of WM_SETTINGCHANGE messages
            // and can respond to display scale changes
            if (! JUCEApplication::isStandaloneApp())
                settingChangeCallback = ComponentPeer::forceDisplayUpdate;
            // Calling this function here is (for some reason) necessary to make Windows
            // correctly enable the menu items that we specify in the wm_initmenu message.
            GetSystemMenu (hwnd, false);
            auto alpha = component.getAlpha();
            if (alpha < 1.0f)
                setAlpha (alpha);
        }
        else
        {
            jassertfalse;
        }
    }
    static BOOL CALLBACK revokeChildDragDropCallback (HWND hwnd, LPARAM)    { RevokeDragDrop (hwnd); return TRUE; }
    static void* destroyWindowCallback (void* handle)
    {
        auto hwnd = reinterpret_cast<HWND> (handle);
        if (IsWindow (hwnd))
        {
            RevokeDragDrop (hwnd);
            // NB: we need to do this before DestroyWindow() as child HWNDs will be invalid after
            EnumChildWindows (hwnd, revokeChildDragDropCallback, 0);
            DestroyWindow (hwnd);
        }
        return nullptr;
    }
    static void* toFrontCallback1 (void* h)
    {
        BringWindowToTop ((HWND) h);
        return nullptr;
    }
    static void* toFrontCallback2 (void* h)
    {
        setWindowZOrder ((HWND) h, HWND_TOP);
        return nullptr;
    }
    static void* setFocusCallback (void* h)
    {
        SetFocus ((HWND) h);
        return nullptr;
    }
    static void* getFocusCallback (void*)
    {
        return GetFocus();
    }
    bool isUsingUpdateLayeredWindow() const
    {
        return ! component.isOpaque();
    }
    bool hasTitleBar() const noexcept        { return (styleFlags & windowHasTitleBar) != 0; }
    void updateShadower()
    {
        if (! component.isCurrentlyModal() && (styleFlags & windowHasDropShadow) != 0
            && ((! hasTitleBar()) || SystemStats::getOperatingSystemType() < SystemStats::WinVista))
        {
            shadower.reset (component.getLookAndFeel().createDropShadowerForComponent (&component));
            if (shadower != nullptr)
                shadower->setOwner (&component);
        }
    }
    void setIcon (const Image& newIcon) override
    {
        if (auto hicon = IconConverters::createHICONFromImage (newIcon, TRUE, 0, 0))
        {
            SendMessage (hwnd, WM_SETICON, ICON_BIG, (LPARAM) hicon);
            SendMessage (hwnd, WM_SETICON, ICON_SMALL, (LPARAM) hicon);
            if (currentWindowIcon != nullptr)
                DestroyIcon (currentWindowIcon);
            currentWindowIcon = hicon;
        }
    }
    void setMessageFilter()
    {
        using ChangeWindowMessageFilterExFunc = BOOL (WINAPI*) (HWND, UINT, DWORD, PVOID);
        if (auto changeMessageFilter = (ChangeWindowMessageFilterExFunc) getUser32Function ("ChangeWindowMessageFilterEx"))
        {
            changeMessageFilter (hwnd, WM_DROPFILES, 1 /*MSGFLT_ALLOW*/, nullptr);
            changeMessageFilter (hwnd, WM_COPYDATA, 1 /*MSGFLT_ALLOW*/, nullptr);
            changeMessageFilter (hwnd, 0x49, 1 /*MSGFLT_ALLOW*/, nullptr);
        }
    }
    struct ChildWindowClippingInfo
    {
        HDC dc;
        HWNDComponentPeer* peer;
        RectangleList<int>* clip;
        Point<int> origin;
        int savedDC;
    };
    static BOOL CALLBACK clipChildWindowCallback (HWND hwnd, LPARAM context)
    {
        if (IsWindowVisible (hwnd))
        {
            auto& info = *(ChildWindowClippingInfo*) context;
            if (GetParent (hwnd) == info.peer->hwnd)
            {
                auto clip = rectangleFromRECT (getWindowClientRect (hwnd));
                info.clip->subtract (clip - info.origin);
                if (info.savedDC == 0)
                    info.savedDC = SaveDC (info.dc);
                ExcludeClipRect (info.dc, clip.getX(), clip.getY(), clip.getRight(), clip.getBottom());
            }
        }
        return TRUE;
    }
    //==============================================================================
    void handlePaintMessage()
    {
       #if JUCE_DIRECT2D
        if (direct2DContext != nullptr)
        {
            RECT r;
            if (GetUpdateRect (hwnd, &r, false))
            {
                direct2DContext->start();
                direct2DContext->clipToRectangle (convertPhysicalScreenRectangleToLogical (rectangleFromRECT (r), hwnd));
                handlePaint (*direct2DContext);
                direct2DContext->end();
                ValidateRect (hwnd, &r);
            }
        }
        else
       #endif
        {
            HRGN rgn = CreateRectRgn (0, 0, 0, 0);
            const int regionType = GetUpdateRgn (hwnd, rgn, false);
            PAINTSTRUCT paintStruct;
            HDC dc = BeginPaint (hwnd, &paintStruct); // Note this can immediately generate a WM_NCPAINT
                                                      // message and become re-entrant, but that's OK
            // if something in a paint handler calls, e.g. a message box, this can become reentrant and
            // corrupt the image it's using to paint into, so do a check here.
            static bool reentrant = false;
            if (! reentrant)
            {
                const ScopedValueSetter<bool> setter (reentrant, true, false);
                if (dontRepaint)
                    component.handleCommandMessage (0); // (this triggers a repaint in the openGL context)
                else
                    performPaint (dc, rgn, regionType, paintStruct);
            }
            DeleteObject (rgn);
            EndPaint (hwnd, &paintStruct);
           #if JUCE_MSVC
            _fpreset(); // because some graphics cards can unmask FP exceptions
           #endif
        }
        lastPaintTime = Time::getMillisecondCounter();
    }
    void performPaint (HDC dc, HRGN rgn, int regionType, PAINTSTRUCT& paintStruct)
    {
        int x = paintStruct.rcPaint.left;
        int y = paintStruct.rcPaint.top;
        int w = paintStruct.rcPaint.right - x;
        int h = paintStruct.rcPaint.bottom - y;
        const bool transparent = isUsingUpdateLayeredWindow();
        if (transparent)
        {
            // it's not possible to have a transparent window with a title bar at the moment!
            jassert (! hasTitleBar());
            auto r = getWindowScreenRect (hwnd);
            x = y = 0;
            w = r.right - r.left;
            h = r.bottom - r.top;
        }
        if (w > 0 && h > 0)
        {
            Image& offscreenImage = offscreenImageGenerator.getImage (transparent, w, h);
            RectangleList<int> contextClip;
            const Rectangle<int> clipBounds (w, h);
            bool needToPaintAll = true;
            if (regionType == COMPLEXREGION && ! transparent)
            {
                HRGN clipRgn = CreateRectRgnIndirect (&paintStruct.rcPaint);
                CombineRgn (rgn, rgn, clipRgn, RGN_AND);
                DeleteObject (clipRgn);
                std::aligned_storage<8192, alignof (RGNDATA)>::type rgnData;
                const DWORD res = GetRegionData (rgn, sizeof (rgnData), (RGNDATA*) &rgnData);
                if (res > 0 && res <= sizeof (rgnData))
                {
                    const RGNDATAHEADER* const hdr = &(((const RGNDATA*) &rgnData)->rdh);
                    if (hdr->iType == RDH_RECTANGLES
                         && hdr->rcBound.right - hdr->rcBound.left >= w
                         && hdr->rcBound.bottom - hdr->rcBound.top >= h)
                    {
                        needToPaintAll = false;
                        auto rects = unalignedPointerCast<const RECT*> ((char*) &rgnData + sizeof (RGNDATAHEADER));
                        for (int i = (int) ((RGNDATA*) &rgnData)->rdh.nCount; --i >= 0;)
                        {
                            if (rects->right <= x + w && rects->bottom <= y + h)
                            {
                                const int cx = jmax (x, (int) rects->left);
                                contextClip.addWithoutMerging (Rectangle<int> (cx - x, rects->top - y,
                                                                               rects->right - cx, rects->bottom - rects->top)
                                                                   .getIntersection (clipBounds));
                            }
                            else
                            {
                                needToPaintAll = true;
                                break;
                            }
                            ++rects;
                        }
                    }
                }
            }
            if (needToPaintAll)
            {
                contextClip.clear();
                contextClip.addWithoutMerging (Rectangle<int> (w, h));
            }
            ChildWindowClippingInfo childClipInfo = { dc, this, &contextClip, Point<int> (x, y), 0 };
            EnumChildWindows (hwnd, clipChildWindowCallback, (LPARAM) &childClipInfo);
            if (! contextClip.isEmpty())
            {
                if (transparent)
                    for (auto& i : contextClip)
                        offscreenImage.clear (i);
                {
                    auto context = component.getLookAndFeel()
                                    .createGraphicsContext (offscreenImage, { -x, -y }, contextClip);
                    context->addTransform (AffineTransform::scale ((float) getPlatformScaleFactor()));
                    handlePaint (*context);
                }
                static_cast<WindowsBitmapImage*> (offscreenImage.getPixelData())
                    ->blitToWindow (hwnd, dc, transparent, x, y, updateLayeredWindowAlpha);
            }
            if (childClipInfo.savedDC != 0)
                RestoreDC (dc, childClipInfo.savedDC);
        }
    }
    //==============================================================================
    void doMouseEvent (Point<float> position, float pressure, float orientation = 0.0f, ModifierKeys mods = ModifierKeys::currentModifiers)
    {
        handleMouseEvent (MouseInputSource::InputSourceType::mouse, position, mods, pressure, orientation, getMouseEventTime());
    }
    StringArray getAvailableRenderingEngines() override
    {
        StringArray s ("Software Renderer");
       #if JUCE_DIRECT2D
        if (SystemStats::getOperatingSystemType() >= SystemStats::Windows7)
            s.add ("Direct2D");
       #endif
        return s;
    }
    int getCurrentRenderingEngine() const override    { return currentRenderingEngine; }
   #if JUCE_DIRECT2D
    void updateDirect2DContext()
    {
        if (currentRenderingEngine != direct2DRenderingEngine)
            direct2DContext = nullptr;
        else if (direct2DContext == nullptr)
            direct2DContext.reset (new Direct2DLowLevelGraphicsContext (hwnd));
    }
   #endif
    void setCurrentRenderingEngine (int index) override
    {
        ignoreUnused (index);
       #if JUCE_DIRECT2D
        if (getAvailableRenderingEngines().size() > 1)
        {
            currentRenderingEngine = index == 1 ? direct2DRenderingEngine : softwareRenderingEngine;
            updateDirect2DContext();
            repaint (component.getLocalBounds());
        }
       #endif
    }
    static uint32 getMinTimeBetweenMouseMoves()
    {
        if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista)
            return 0;
        return 1000 / 60;  // Throttling the incoming mouse-events seems to still be needed in XP..
    }
    bool isTouchEvent() noexcept
    {
        if (registerTouchWindow == nullptr)
            return false;
        // Relevant info about touch/pen detection flags:
        // https://msdn.microsoft.com/en-us/library/windows/desktop/ms703320(v=vs.85).aspx
        // http://www.petertissen.de/?p=4
        return ((uint32_t) GetMessageExtraInfo() & 0xFFFFFF80 /*SIGNATURE_MASK*/) == 0xFF515780 /*MI_WP_SIGNATURE*/;
    }
    static bool areOtherTouchSourcesActive()
    {
        for (auto& ms : Desktop::getInstance().getMouseSources())
            if (ms.isDragging() && (ms.getType() == MouseInputSource::InputSourceType::touch
                                     || ms.getType() == MouseInputSource::InputSourceType::pen))
                return true;
        return false;
    }
    void doMouseMove (Point<float> position, bool isMouseDownEvent)
    {
        ModifierKeys modsToSend (ModifierKeys::currentModifiers);
        // this will be handled by WM_TOUCH
        if (isTouchEvent() || areOtherTouchSourcesActive())
            return;
        if (! isMouseOver)
        {
            isMouseOver = true;
            // This avoids a rare stuck-button problem when focus is lost unexpectedly, but must
            // not be called as part of a move, in case it's actually a mouse-drag from another
            // app which ends up here when we get focus before the mouse is released..
            if (isMouseDownEvent && getNativeRealtimeModifiers != nullptr)
                getNativeRealtimeModifiers();
            updateKeyModifiers();
           #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client
            if (modProvider != nullptr)
                ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withFlags (modProvider->getWin32Modifiers());
           #endif
            TRACKMOUSEEVENT tme;
            tme.cbSize = sizeof (tme);
            tme.dwFlags = TME_LEAVE;
            tme.hwndTrack = hwnd;
            tme.dwHoverTime = 0;
            if (! TrackMouseEvent (&tme))
                jassertfalse;
            Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate();
        }
        else if (! isDragging)
        {
            if (! contains (position.roundToInt(), false))
                return;
        }
        static uint32 lastMouseTime = 0;
        static auto minTimeBetweenMouses = getMinTimeBetweenMouseMoves();
        auto now = Time::getMillisecondCounter();
        if (! Desktop::getInstance().getMainMouseSource().isDragging())
            modsToSend = modsToSend.withoutMouseButtons();
        if (now >= lastMouseTime + minTimeBetweenMouses)
        {
            lastMouseTime = now;
            doMouseEvent (position, MouseInputSource::invalidPressure,
                          MouseInputSource::invalidOrientation, modsToSend);
        }
    }
    void doMouseDown (Point<float> position, const WPARAM wParam)
    {
        // this will be handled by WM_TOUCH
        if (isTouchEvent() || areOtherTouchSourcesActive())
            return;
        if (GetCapture() != hwnd)
            SetCapture (hwnd);
        doMouseMove (position, true);
        if (isValidPeer (this))
        {
            updateModifiersFromWParam (wParam);
           #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client
            if (modProvider != nullptr)
                ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withFlags (modProvider->getWin32Modifiers());
           #endif
            isDragging = true;
            doMouseEvent (position, MouseInputSource::invalidPressure);
        }
    }
    void doMouseUp (Point<float> position, const WPARAM wParam)
    {
        // this will be handled by WM_TOUCH
        if (isTouchEvent() || areOtherTouchSourcesActive())
            return;
        updateModifiersFromWParam (wParam);
       #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client
        if (modProvider != nullptr)
            ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withFlags (modProvider->getWin32Modifiers());
       #endif
        const bool wasDragging = isDragging;
        isDragging = false;
        // release the mouse capture if the user has released all buttons
        if ((wParam & (MK_LBUTTON | MK_RBUTTON | MK_MBUTTON)) == 0 && hwnd == GetCapture())
            ReleaseCapture();
        // NB: under some circumstances (e.g. double-clicking a native title bar), a mouse-up can
        // arrive without a mouse-down, so in that case we need to avoid sending a message.
        if (wasDragging)
            doMouseEvent (position, MouseInputSource::invalidPressure);
    }
    void doCaptureChanged()
    {
        if (constrainerIsResizing)
        {
            if (constrainer != nullptr)
                constrainer->resizeEnd();
            constrainerIsResizing = false;
        }
        if (isDragging)
            doMouseUp (getCurrentMousePos(), (WPARAM) 0);
    }
    void doMouseExit()
    {
        isMouseOver = false;
        if (! areOtherTouchSourcesActive())
            doMouseEvent (getCurrentMousePos(), MouseInputSource::invalidPressure);
    }
    ComponentPeer* findPeerUnderMouse (Point<float>& localPos)
    {
        auto currentMousePos = getPOINTFromLParam ((LPARAM) GetMessagePos());
        // Because Windows stupidly sends all wheel events to the window with the keyboard
        // focus, we have to redirect them here according to the mouse pos..
        auto* peer = getOwnerOfWindow (WindowFromPoint (currentMousePos));
        if (peer == nullptr)
            peer = this;
        localPos = peer->globalToLocal (convertPhysicalScreenPointToLogical (pointFromPOINT (currentMousePos), hwnd).toFloat());
        return peer;
    }
    static MouseInputSource::InputSourceType getPointerType (WPARAM wParam)
    {
        if (getPointerTypeFunction != nullptr)
        {
            POINTER_INPUT_TYPE pointerType;
            if (getPointerTypeFunction (GET_POINTERID_WPARAM (wParam), &pointerType))
            {
                if (pointerType == 2)
                    return MouseInputSource::InputSourceType::touch;
                if (pointerType == 3)
                    return MouseInputSource::InputSourceType::pen;
            }
        }
        return MouseInputSource::InputSourceType::mouse;
    }
    void doMouseWheel (const WPARAM wParam, const bool isVertical)
    {
        updateKeyModifiers();
        const float amount = jlimit (-1000.0f, 1000.0f, 0.5f * (short) HIWORD (wParam));
        MouseWheelDetails wheel;
        wheel.deltaX = isVertical ? 0.0f : amount / -256.0f;
        wheel.deltaY = isVertical ? amount / 256.0f : 0.0f;
        wheel.isReversed = false;
        wheel.isSmooth = false;
        wheel.isInertial = false;
        Point<float> localPos;
        if (auto* peer = findPeerUnderMouse (localPos))
            peer->handleMouseWheel (getPointerType (wParam), localPos, getMouseEventTime(), wheel);
    }
    bool doGestureEvent (LPARAM lParam)
    {
        GESTUREINFO gi;
        zerostruct (gi);
        gi.cbSize = sizeof (gi);
        if (getGestureInfo != nullptr && getGestureInfo ((HGESTUREINFO) lParam, &gi))
        {
            updateKeyModifiers();
            Point<float> localPos;
            if (auto* peer = findPeerUnderMouse (localPos))
            {
                switch (gi.dwID)
                {
                    case 3: /*GID_ZOOM*/
                        if (gi.dwFlags != 1 /*GF_BEGIN*/ && lastMagnifySize > 0)
                            peer->handleMagnifyGesture (MouseInputSource::InputSourceType::touch, localPos, getMouseEventTime(),
                                                        (float) ((double) gi.ullArguments / (double) lastMagnifySize));
                        lastMagnifySize = gi.ullArguments;
                        return true;
                    case 4: /*GID_PAN*/
                    case 5: /*GID_ROTATE*/
                    case 6: /*GID_TWOFINGERTAP*/
                    case 7: /*GID_PRESSANDTAP*/
                    default:
                        break;
                }
            }
        }
        return false;
    }
    LRESULT doTouchEvent (const int numInputs, HTOUCHINPUT eventHandle)
    {
        if ((styleFlags & windowIgnoresMouseClicks) != 0)
            if (auto* parent = getOwnerOfWindow (GetParent (hwnd)))
                if (parent != this)
                    return parent->doTouchEvent (numInputs, eventHandle);
        HeapBlock<TOUCHINPUT> inputInfo (numInputs);
        if (getTouchInputInfo (eventHandle, (UINT) numInputs, inputInfo, sizeof (TOUCHINPUT)))
        {
            for (int i = 0; i < numInputs; ++i)
            {
                auto flags = inputInfo[i].dwFlags;
                if ((flags & (TOUCHEVENTF_DOWN | TOUCHEVENTF_MOVE | TOUCHEVENTF_UP)) != 0)
                    if (! handleTouchInput (inputInfo[i], (flags & TOUCHEVENTF_DOWN) != 0, (flags & TOUCHEVENTF_UP) != 0))
                        return 0;  // abandon method if this window was deleted by the callback
            }
        }
        closeTouchInputHandle (eventHandle);
        return 0;
    }
    bool handleTouchInput (const TOUCHINPUT& touch, const bool isDown, const bool isUp,
                           const float touchPressure = MouseInputSource::invalidPressure,
                           const float orientation = 0.0f)
    {
        auto isCancel = false;
        const auto touchIndex = currentTouches.getIndexOfTouch (this, touch.dwID);
        const auto time = getMouseEventTime();
        const auto pos = globalToLocal (convertPhysicalScreenPointToLogical (pointFromPOINT ({ roundToInt (touch.x / 100.0f),
                                                                                               roundToInt (touch.y / 100.0f) }), hwnd).toFloat());
        const auto pressure = touchPressure;
        auto modsToSend = ModifierKeys::currentModifiers;
        if (isDown)
        {
            ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier);
            modsToSend = ModifierKeys::currentModifiers;
            // this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before.
            handleMouseEvent (MouseInputSource::InputSourceType::touch, pos, modsToSend.withoutMouseButtons(),
                              pressure, orientation, time, {}, touchIndex);
            if (! isValidPeer (this)) // (in case this component was deleted by the event)
                return false;
        }
        else if (isUp)
        {
            modsToSend = modsToSend.withoutMouseButtons();
            ModifierKeys::currentModifiers = modsToSend;
            currentTouches.clearTouch (touchIndex);
            if (! currentTouches.areAnyTouchesActive())
                isCancel = true;
        }
        else
        {
            modsToSend = ModifierKeys::currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier);
        }
        handleMouseEvent (MouseInputSource::InputSourceType::touch, pos, modsToSend,
                          pressure, orientation, time, {}, touchIndex);
        if (! isValidPeer (this))
            return false;
        if (isUp)
        {
            handleMouseEvent (MouseInputSource::InputSourceType::touch, MouseInputSource::offscreenMousePos, ModifierKeys::currentModifiers.withoutMouseButtons(),
                              pressure, orientation, time, {}, touchIndex);
            if (! isValidPeer (this))
                return false;
            if (isCancel)
            {
                currentTouches.clear();
                ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons();
            }
        }
        return true;
    }
    bool handlePointerInput (WPARAM wParam, LPARAM lParam, const bool isDown, const bool isUp)
    {
        if (! canUsePointerAPI)
            return false;
        auto pointerType = getPointerType (wParam);
        if (pointerType == MouseInputSource::InputSourceType::touch)
        {
            POINTER_TOUCH_INFO touchInfo;
            if (! getPointerTouchInfo (GET_POINTERID_WPARAM (wParam), &touchInfo))
                return false;
            const auto pressure = touchInfo.touchMask & TOUCH_MASK_PRESSURE ? static_cast<float> (touchInfo.pressure)
                                                                            : MouseInputSource::invalidPressure;
            const auto orientation = touchInfo.touchMask & TOUCH_MASK_ORIENTATION ? degreesToRadians (static_cast<float> (touchInfo.orientation))
                                                                                  : MouseInputSource::invalidOrientation;
            if (! handleTouchInput (emulateTouchEventFromPointer (touchInfo.pointerInfo.ptPixelLocationRaw, wParam),
                                    isDown, isUp, pressure, orientation))
                return false;
        }
        else if (pointerType == MouseInputSource::InputSourceType::pen)
        {
            POINTER_PEN_INFO penInfo;
            if (! getPointerPenInfo (GET_POINTERID_WPARAM (wParam), &penInfo))
                return false;
            const auto pressure = (penInfo.penMask & PEN_MASK_PRESSURE) ? (float) penInfo.pressure / 1024.0f : MouseInputSource::invalidPressure;
            if (! handlePenInput (penInfo, globalToLocal (convertPhysicalScreenPointToLogical (pointFromPOINT (getPOINTFromLParam (lParam)), hwnd).toFloat()),
                                  pressure, isDown, isUp))
                return false;
        }
        else
        {
            return false;
        }
        return true;
    }
    TOUCHINPUT emulateTouchEventFromPointer (POINT p, WPARAM wParam)
    {
        TOUCHINPUT touchInput;
        touchInput.dwID = GET_POINTERID_WPARAM (wParam);
        touchInput.x = p.x * 100;
        touchInput.y = p.y * 100;
        return touchInput;
    }
    bool handlePenInput (POINTER_PEN_INFO penInfo, Point<float> pos, const float pressure, bool isDown, bool isUp)
    {
        const auto time = getMouseEventTime();
        ModifierKeys modsToSend (ModifierKeys::currentModifiers);
        PenDetails penDetails;
        penDetails.rotation = (penInfo.penMask & PEN_MASK_ROTATION) ? degreesToRadians (static_cast<float> (penInfo.rotation)) : MouseInputSource::invalidRotation;
        penDetails.tiltX = (penInfo.penMask & PEN_MASK_TILT_X) ? (float) penInfo.tiltX / 90.0f : MouseInputSource::invalidTiltX;
        penDetails.tiltY = (penInfo.penMask & PEN_MASK_TILT_Y) ? (float) penInfo.tiltY / 90.0f : MouseInputSource::invalidTiltY;
        auto pInfoFlags = penInfo.pointerInfo.pointerFlags;
        if ((pInfoFlags & POINTER_FLAG_FIRSTBUTTON) != 0)
            ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier);
        else if ((pInfoFlags & POINTER_FLAG_SECONDBUTTON) != 0)
            ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::rightButtonModifier);
        if (isDown)
        {
            modsToSend = ModifierKeys::currentModifiers;
            // this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before.
            handleMouseEvent (MouseInputSource::InputSourceType::pen, pos, modsToSend.withoutMouseButtons(),
                              pressure, MouseInputSource::invalidOrientation, time, penDetails);
            if (! isValidPeer (this)) // (in case this component was deleted by the event)
                return false;
        }
        else if (isUp || ! (pInfoFlags & POINTER_FLAG_INCONTACT))
        {
            modsToSend = modsToSend.withoutMouseButtons();
            ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons();
        }
        handleMouseEvent (MouseInputSource::InputSourceType::pen, pos, modsToSend, pressure,
                          MouseInputSource::invalidOrientation, time, penDetails);
        if (! isValidPeer (this)) // (in case this component was deleted by the event)
            return false;
        if (isUp)
        {
            handleMouseEvent (MouseInputSource::InputSourceType::pen, MouseInputSource::offscreenMousePos, ModifierKeys::currentModifiers,
                              pressure, MouseInputSource::invalidOrientation, time, penDetails);
            if (! isValidPeer (this))
                return false;
        }
        return true;
    }
    //==============================================================================
    void sendModifierKeyChangeIfNeeded()
    {
        if (modifiersAtLastCallback != ModifierKeys::currentModifiers)
        {
            modifiersAtLastCallback = ModifierKeys::currentModifiers;
            handleModifierKeysChange();
        }
    }
    bool doKeyUp (const WPARAM key)
    {
        updateKeyModifiers();
        switch (key)
        {
            case VK_SHIFT:
            case VK_CONTROL:
            case VK_MENU:
            case VK_CAPITAL:
            case VK_LWIN:
            case VK_RWIN:
            case VK_APPS:
            case VK_NUMLOCK:
            case VK_SCROLL:
            case VK_LSHIFT:
            case VK_RSHIFT:
            case VK_LCONTROL:
            case VK_LMENU:
            case VK_RCONTROL:
            case VK_RMENU:
                sendModifierKeyChangeIfNeeded();
        }
        return handleKeyUpOrDown (false)
                || Component::getCurrentlyModalComponent() != nullptr;
    }
    bool doKeyDown (const WPARAM key)
    {
        updateKeyModifiers();
        bool used = false;
        switch (key)
        {
            case VK_SHIFT:
            case VK_LSHIFT:
            case VK_RSHIFT:
            case VK_CONTROL:
            case VK_LCONTROL:
            case VK_RCONTROL:
            case VK_MENU:
            case VK_LMENU:
            case VK_RMENU:
            case VK_LWIN:
            case VK_RWIN:
            case VK_CAPITAL:
            case VK_NUMLOCK:
            case VK_SCROLL:
            case VK_APPS:
                used = handleKeyUpOrDown (true);
                sendModifierKeyChangeIfNeeded();
                break;
            case VK_LEFT:
            case VK_RIGHT:
            case VK_UP:
            case VK_DOWN:
            case VK_PRIOR:
            case VK_NEXT:
            case VK_HOME:
            case VK_END:
            case VK_DELETE:
            case VK_INSERT:
            case VK_F1:
            case VK_F2:
            case VK_F3:
            case VK_F4:
            case VK_F5:
            case VK_F6:
            case VK_F7:
            case VK_F8:
            case VK_F9:
            case VK_F10:
            case VK_F11:
            case VK_F12:
            case VK_F13:
            case VK_F14:
            case VK_F15:
            case VK_F16:
            case VK_F17:
            case VK_F18:
            case VK_F19:
            case VK_F20:
            case VK_F21:
            case VK_F22:
            case VK_F23:
            case VK_F24:
                used = handleKeyUpOrDown (true);
                used = handleKeyPress (extendedKeyModifier | (int) key, 0) || used;
                break;
            default:
                used = handleKeyUpOrDown (true);
                {
                    MSG msg;
                    if (! PeekMessage (&msg, hwnd, WM_CHAR, WM_DEADCHAR, PM_NOREMOVE))
                    {
                        // if there isn't a WM_CHAR or WM_DEADCHAR message pending, we need to
                        // manually generate the key-press event that matches this key-down.
                        const UINT keyChar  = MapVirtualKey ((UINT) key, 2);
                        const UINT scanCode = MapVirtualKey ((UINT) key, 0);
                        BYTE keyState[256];
                        ignoreUnused (GetKeyboardState (keyState));
                        WCHAR text[16] = { 0 };
                        if (ToUnicode ((UINT) key, scanCode, keyState, text, 8, 0) != 1)
                            text[0] = 0;
                        used = handleKeyPress ((int) LOWORD (keyChar), (juce_wchar) text[0]) || used;
                    }
                }
                break;
        }
        return used || (Component::getCurrentlyModalComponent() != nullptr);
    }
    bool doKeyChar (int key, const LPARAM flags)
    {
        updateKeyModifiers();
        auto textChar = (juce_wchar) key;
        const int virtualScanCode = (flags >> 16) & 0xff;
        if (key >= '0' && key <= '9')
        {
            switch (virtualScanCode)  // check for a numeric keypad scan-code
            {
                case 0x52:
                case 0x4f:
                case 0x50:
                case 0x51:
                case 0x4b:
                case 0x4c:
                case 0x4d:
                case 0x47:
                case 0x48:
                case 0x49:
                    key = (key - '0') + KeyPress::numberPad0;
                    break;
                default:
                    break;
            }
        }
        else
        {
            // convert the scan code to an unmodified character code..
            const UINT virtualKey = MapVirtualKey ((UINT) virtualScanCode, 1);
            UINT keyChar = MapVirtualKey (virtualKey, 2);
            keyChar = LOWORD (keyChar);
            if (keyChar != 0)
                key = (int) keyChar;
            // avoid sending junk text characters for some control-key combinations
            if (textChar < ' ' && ModifierKeys::currentModifiers.testFlags (ModifierKeys::ctrlModifier | ModifierKeys::altModifier))
                textChar = 0;
        }
        return handleKeyPress (key, textChar);
    }
    void forwardMessageToParent (UINT message, WPARAM wParam, LPARAM lParam) const
    {
        if (HWND parentH = GetParent (hwnd))
            PostMessage (parentH, message, wParam, lParam);
    }
    bool doAppCommand (const LPARAM lParam)
    {
        int key = 0;
        switch (GET_APPCOMMAND_LPARAM (lParam))
        {
            case APPCOMMAND_MEDIA_PLAY_PAUSE:       key = KeyPress::playKey; break;
            case APPCOMMAND_MEDIA_STOP:             key = KeyPress::stopKey; break;
            case APPCOMMAND_MEDIA_NEXTTRACK:        key = KeyPress::fastForwardKey; break;
            case APPCOMMAND_MEDIA_PREVIOUSTRACK:    key = KeyPress::rewindKey; break;
            default: break;
        }
        if (key != 0)
        {
            updateKeyModifiers();
            if (hwnd == GetActiveWindow())
                return handleKeyPress (key, 0);
        }
        return false;
    }
    bool isConstrainedNativeWindow() const
    {
        return constrainer != nullptr
                && (styleFlags & (windowHasTitleBar | windowIsResizable)) == (windowHasTitleBar | windowIsResizable)
                && ! isKioskMode();
    }
    Rectangle<int> getCurrentScaledBounds() const
    {
        return ScalingHelpers::unscaledScreenPosToScaled (component, windowBorder.addedTo (ScalingHelpers::scaledScreenPosToUnscaled (component, component.getBounds())));
    }
    LRESULT handleSizeConstraining (RECT& r, const WPARAM wParam)
    {
        if (isConstrainedNativeWindow())
        {
            const auto logicalBounds = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (r).toFloat(), hwnd);
            auto pos = ScalingHelpers::unscaledScreenPosToScaled (component, logicalBounds).toNearestInt();
            const auto original = getCurrentScaledBounds();
            constrainer->checkBounds (pos, original,
                                      Desktop::getInstance().getDisplays().getTotalBounds (true),
                                      wParam == WMSZ_TOP    || wParam == WMSZ_TOPLEFT    || wParam == WMSZ_TOPRIGHT,
                                      wParam == WMSZ_LEFT   || wParam == WMSZ_TOPLEFT    || wParam == WMSZ_BOTTOMLEFT,
                                      wParam == WMSZ_BOTTOM || wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_BOTTOMRIGHT,
                                      wParam == WMSZ_RIGHT  || wParam == WMSZ_TOPRIGHT   || wParam == WMSZ_BOTTOMRIGHT);
            r = RECTFromRectangle (convertLogicalScreenRectangleToPhysical (ScalingHelpers::scaledScreenPosToUnscaled (component, pos.toFloat()).toNearestInt(), hwnd));
        }
        return TRUE;
    }
    LRESULT handlePositionChanging (WINDOWPOS& wp)
    {
        if (isConstrainedNativeWindow() && ! isFullScreen())
        {
            if ((wp.flags & (SWP_NOMOVE | SWP_NOSIZE)) != (SWP_NOMOVE | SWP_NOSIZE)
                 && (wp.x > -32000 && wp.y > -32000)
                 && ! Component::isMouseButtonDownAnywhere())
            {
                const auto logicalBounds = convertPhysicalScreenRectangleToLogical (rectangleFromRECT ({ wp.x, wp.y, wp.x + wp.cx, wp.y + wp.cy }).toFloat(), hwnd);
                auto pos = ScalingHelpers::unscaledScreenPosToScaled (component, logicalBounds).toNearestInt();
                const auto original = getCurrentScaledBounds();
                constrainer->checkBounds (pos, original,
                                          Desktop::getInstance().getDisplays().getTotalBounds (true),
                                          pos.getY() != original.getY() && pos.getBottom() == original.getBottom(),
                                          pos.getX() != original.getX() && pos.getRight()  == original.getRight(),
                                          pos.getY() == original.getY() && pos.getBottom() != original.getBottom(),
                                          pos.getX() == original.getX() && pos.getRight()  != original.getRight());
                auto physicalBounds = convertLogicalScreenRectangleToPhysical (ScalingHelpers::scaledScreenPosToUnscaled (component, pos.toFloat()), hwnd);
                auto getNewPositionIfNotRoundingError = [] (int posIn, float newPos)
                {
                    return (std::abs ((float) posIn - newPos) >= 1.0f) ? roundToInt (newPos) : posIn;
                };
                wp.x  = getNewPositionIfNotRoundingError (wp.x,  physicalBounds.getX());
                wp.y  = getNewPositionIfNotRoundingError (wp.y,  physicalBounds.getY());
                wp.cx = getNewPositionIfNotRoundingError (wp.cx, physicalBounds.getWidth());
                wp.cy = getNewPositionIfNotRoundingError (wp.cy, physicalBounds.getHeight());
            }
        }
        if (((wp.flags & SWP_SHOWWINDOW) != 0 && ! component.isVisible()))
            component.setVisible (true);
        else if (((wp.flags & SWP_HIDEWINDOW) != 0 && component.isVisible()))
            component.setVisible (false);
        return 0;
    }
    bool handlePositionChanged()
    {
        auto pos = getCurrentMousePos();
        if (contains (pos.roundToInt(), false))
        {
            if (! areOtherTouchSourcesActive())
                doMouseEvent (pos, MouseInputSource::invalidPressure);
            if (! isValidPeer (this))
                return true;
        }
        handleMovedOrResized();
        return ! dontRepaint; // to allow non-accelerated openGL windows to draw themselves correctly.
    }
    //==============================================================================
    LRESULT handleDPIChanging (int newDPI, RECT newRect)
    {
        // Sometimes, windows that should not be automatically scaled (secondary windows in plugins)
        // are sent WM_DPICHANGED. The size suggested by the OS is incorrect for our unscaled
        // window, so we should ignore it.
        if (! isPerMonitorDPIAwareWindow (hwnd))
            return 0;
        const auto newScale = (double) newDPI / USER_DEFAULT_SCREEN_DPI;
        if (approximatelyEqual (scaleFactor, newScale))
            return 0;
        scaleFactor = newScale;
        {
            const ScopedValueSetter<int> setter (numInDpiChange, numInDpiChange + 1);
            setBounds (windowBorder.subtractedFrom (convertPhysicalScreenRectangleToLogical (rectangleFromRECT (newRect), hwnd)), fullScreen);
        }
        // This is to handle reentrancy. If responding to a DPI change triggers further DPI changes,
        // we should only notify listeners and resize windows once all of the DPI changes have
        // resolved.
        if (numInDpiChange != 0)
            return 0;
        updateShadower();
        InvalidateRect (hwnd, nullptr, FALSE);
        scaleFactorListeners.call ([this] (ScaleFactorListener& l) { l.nativeScaleFactorChanged (scaleFactor); });
        return 0;
    }
    //==============================================================================
    void handleAppActivation (const WPARAM wParam)
    {
        modifiersAtLastCallback = -1;
        updateKeyModifiers();
        if (isMinimised())
        {
            component.repaint();
            handleMovedOrResized();
            if (! isValidPeer (this))
                return;
        }
        auto* underMouse = component.getComponentAt (component.getMouseXYRelative());
        if (underMouse == nullptr)
            underMouse = &component;
        if (underMouse->isCurrentlyBlockedByAnotherModalComponent())
        {
            if (LOWORD (wParam) == WA_CLICKACTIVE)
                Component::getCurrentlyModalComponent()->inputAttemptWhenModal();
            else
                ModalComponentManager::getInstance()->bringModalComponentsToFront();
        }
        else
        {
            handleBroughtToFront();
        }
    }
    void handlePowerBroadcast (WPARAM wParam)
    {
        if (auto* app = JUCEApplicationBase::getInstance())
        {
            switch (wParam)
            {
                case PBT_APMSUSPEND:                app->suspended(); break;
                case PBT_APMQUERYSUSPENDFAILED:
                case PBT_APMRESUMECRITICAL:
                case PBT_APMRESUMESUSPEND:
                case PBT_APMRESUMEAUTOMATIC:        app->resumed(); break;
                default: break;
            }
        }
    }
    void handleLeftClickInNCArea (WPARAM wParam)
    {
        if (! sendInputAttemptWhenModalMessage())
        {
            switch (wParam)
            {
            case HTBOTTOM:
            case HTBOTTOMLEFT:
            case HTBOTTOMRIGHT:
            case HTGROWBOX:
            case HTLEFT:
            case HTRIGHT:
            case HTTOP:
            case HTTOPLEFT:
            case HTTOPRIGHT:
                if (isConstrainedNativeWindow())
                {
                    constrainerIsResizing = true;
                    constrainer->resizeStart();
                }
                break;
            default:
                break;
            }
        }
    }
    void initialiseSysMenu (HMENU menu) const
    {
        if (! hasTitleBar())
        {
            if (isFullScreen())
            {
                EnableMenuItem (menu, SC_RESTORE,  MF_BYCOMMAND | MF_ENABLED);
                EnableMenuItem (menu, SC_MOVE,     MF_BYCOMMAND | MF_GRAYED);
            }
            else if (! isMinimised())
            {
                EnableMenuItem (menu, SC_MAXIMIZE, MF_BYCOMMAND | MF_GRAYED);
            }
        }
    }
    void doSettingChange()
    {
        forceDisplayUpdate();
        if (fullScreen && ! isMinimised())
            setWindowPos (hwnd, ScalingHelpers::scaledScreenPosToUnscaled (component, Desktop::getInstance().getDisplays()
                                                                                              .getDisplayForRect (component.getScreenBounds())->userArea),
                          SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOSENDCHANGING);
    }
    //==============================================================================
   #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client
    void setModifierKeyProvider (ModifierKeyProvider* provider) override
    {
        modProvider = provider;
    }
    void removeModifierKeyProvider() override
    {
        modProvider = nullptr;
    }
   #endif
public:
    static LRESULT CALLBACK windowProc (HWND h, UINT message, WPARAM wParam, LPARAM lParam)
    {
        // Ensure that non-client areas are scaled for per-monitor DPI awareness v1 - can't
        // do this in peerWindowProc as we have no window at this point
        if (message == WM_NCCREATE && enableNonClientDPIScaling != nullptr)
            enableNonClientDPIScaling (h);
        if (auto* peer = getOwnerOfWindow (h))
        {
            jassert (isValidPeer (peer));
            return peer->peerWindowProc (h, message, wParam, lParam);
        }
        return DefWindowProcW (h, message, wParam, lParam);
    }
private:
    static void* callFunctionIfNotLocked (MessageCallbackFunction* callback, void* userData)
    {
        auto& mm = *MessageManager::getInstance();
        if (mm.currentThreadHasLockedMessageManager())
            return callback (userData);
        return mm.callFunctionOnMessageThread (callback, userData);
    }
    static POINT getPOINTFromLParam (LPARAM lParam) noexcept
    {
        return { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) };
    }
    Point<float> getPointFromLocalLParam (LPARAM lParam) noexcept
    {
        auto p = pointFromPOINT (getPOINTFromLParam (lParam));
        if (isPerMonitorDPIAwareWindow (hwnd))
        {
            // LPARAM is relative to this window's top-left but may be on a different monitor so we need to calculate the
            // physical screen position and then convert this to local logical coordinates
            auto r = getWindowScreenRect (hwnd);
            return globalToLocal (Desktop::getInstance().getDisplays().physicalToLogical (pointFromPOINT ({ r.left + p.x + roundToInt (windowBorder.getLeft() * scaleFactor),
                                                                                                            r.top  + p.y + roundToInt (windowBorder.getTop()  * scaleFactor) })).toFloat());
        }
        return p.toFloat();
    }
    Point<float> getCurrentMousePos() noexcept
    {
        return globalToLocal (convertPhysicalScreenPointToLogical (pointFromPOINT (getPOINTFromLParam ((LPARAM) GetMessagePos())), hwnd).toFloat());
    }
    LRESULT peerWindowProc (HWND h, UINT message, WPARAM wParam, LPARAM lParam)
    {
        switch (message)
        {
            //==============================================================================
            case WM_NCHITTEST:
                if ((styleFlags & windowIgnoresMouseClicks) != 0)
                    return HTTRANSPARENT;
                if (! hasTitleBar())
                    return HTCLIENT;
                break;
            //==============================================================================
            case WM_PAINT:
                handlePaintMessage();
                return 0;
            case WM_NCPAINT:
                handlePaintMessage(); // this must be done, even with native titlebars, or there are rendering artifacts.
                if (hasTitleBar())
                    break; // let the DefWindowProc handle drawing the frame.
                return 0;
            case WM_ERASEBKGND:
            case WM_NCCALCSIZE:
                if (hasTitleBar())
                    break;
                return 1;
            //==============================================================================
            case WM_POINTERUPDATE:
                if (handlePointerInput (wParam, lParam, false, false))
                    return 0;
                break;
            case WM_POINTERDOWN:
                if (handlePointerInput (wParam, lParam, true, false))
                    return 0;
                break;
            case WM_POINTERUP:
                if (handlePointerInput (wParam, lParam, false, true))
                    return 0;
                break;
            //==============================================================================
            case WM_MOUSEMOVE:          doMouseMove (getPointFromLocalLParam (lParam), false); return 0;
            case WM_POINTERLEAVE:
            case WM_MOUSELEAVE:         doMouseExit(); return 0;
            case WM_LBUTTONDOWN:
            case WM_MBUTTONDOWN:
            case WM_RBUTTONDOWN:        doMouseDown (getPointFromLocalLParam (lParam), wParam); return 0;
            case WM_LBUTTONUP:
            case WM_MBUTTONUP:
            case WM_RBUTTONUP:          doMouseUp (getPointFromLocalLParam (lParam), wParam); return 0;
            case WM_POINTERWHEEL:
            case 0x020A: /* WM_MOUSEWHEEL */   doMouseWheel (wParam, true);  return 0;
            case WM_POINTERHWHEEL:
            case 0x020E: /* WM_MOUSEHWHEEL */  doMouseWheel (wParam, false); return 0;
            case WM_CAPTURECHANGED:     doCaptureChanged(); return 0;
            case WM_NCPOINTERUPDATE:
            case WM_NCMOUSEMOVE:
                if (hasTitleBar())
                    break;
                return 0;
            case WM_TOUCH:
                if (getTouchInputInfo != nullptr)
                    return doTouchEvent ((int) wParam, (HTOUCHINPUT) lParam);
                break;
            case 0x119: /* WM_GESTURE */
                if (doGestureEvent (lParam))
                    return 0;
                break;
            //==============================================================================
            case WM_SIZING:                  return handleSizeConstraining (*(RECT*) lParam, wParam);
            case WM_WINDOWPOSCHANGING:       return handlePositionChanging (*(WINDOWPOS*) lParam);
            case 0x2e0: /* WM_DPICHANGED */  return handleDPIChanging ((int) HIWORD (wParam), *(RECT*) lParam);
            case WM_WINDOWPOSCHANGED:
            {
                const WINDOWPOS& wPos = *reinterpret_cast<WINDOWPOS*> (lParam);
                if ((wPos.flags & SWP_NOMOVE) != 0 && (wPos.flags & SWP_NOSIZE) != 0)
                    startTimer (100);
                else
                    if (handlePositionChanged())
                        return 0;
            }
            break;
            //==============================================================================
            case WM_KEYDOWN:
            case WM_SYSKEYDOWN:
                if (doKeyDown (wParam))
                    return 0;
                forwardMessageToParent (message, wParam, lParam);
                break;
            case WM_KEYUP:
            case WM_SYSKEYUP:
                if (doKeyUp (wParam))
                    return 0;
                forwardMessageToParent (message, wParam, lParam);
                break;
            case WM_CHAR:
                if (doKeyChar ((int) wParam, lParam))
                    return 0;
                forwardMessageToParent (message, wParam, lParam);
                break;
            case WM_APPCOMMAND:
                if (doAppCommand (lParam))
                    return TRUE;
                break;
            case WM_MENUCHAR: // triggered when alt+something is pressed
                return MNC_CLOSE << 16; // (avoids making the default system beep)
            //==============================================================================
            case WM_SETFOCUS:
                updateKeyModifiers();
                handleFocusGain();
                break;
            case WM_KILLFOCUS:
                if (hasCreatedCaret)
                {
                    hasCreatedCaret = false;
                    DestroyCaret();
                }
                handleFocusLoss();
                if (auto* modal = Component::getCurrentlyModalComponent())
                    if (auto* peer = modal->getPeer())
                        if ((peer->getStyleFlags() & ComponentPeer::windowIsTemporary) != 0)
                            sendInputAttemptWhenModalMessage();
                break;
            case WM_ACTIVATEAPP:
                // Windows does weird things to process priority when you swap apps,
                // so this forces an update when the app is brought to the front
                if (wParam != FALSE)
                    juce_repeatLastProcessPriority();
                else
                    Desktop::getInstance().setKioskModeComponent (nullptr); // turn kiosk mode off if we lose focus
                juce_checkCurrentlyFocusedTopLevelWindow();
                modifiersAtLastCallback = -1;
                return 0;
            case WM_ACTIVATE:
                if (LOWORD (wParam) == WA_ACTIVE || LOWORD (wParam) == WA_CLICKACTIVE)
                {
                    handleAppActivation (wParam);
                    return 0;
                }
                break;
            case WM_NCACTIVATE:
                // while a temporary window is being shown, prevent Windows from deactivating the
                // title bars of our main windows.
                if (wParam == 0 && ! shouldDeactivateTitleBar)
                    wParam = TRUE; // change this and let it get passed to the DefWindowProc.
                break;
            case WM_POINTERACTIVATE:
            case WM_MOUSEACTIVATE:
                if (! component.getMouseClickGrabsKeyboardFocus())
                    return MA_NOACTIVATE;
                break;
            case WM_SHOWWINDOW:
                if (wParam != 0)
                {
                    component.setVisible (true);
                    handleBroughtToFront();
                }
                break;
            case WM_CLOSE:
                if (! component.isCurrentlyBlockedByAnotherModalComponent())
                    handleUserClosingWindow();
                return 0;
           #if JUCE_REMOVE_COMPONENT_FROM_DESKTOP_ON_WM_DESTROY
            case WM_DESTROY:
                getComponent().removeFromDesktop();
                return 0;
           #endif
            case WM_QUERYENDSESSION:
                if (auto* app = JUCEApplicationBase::getInstance())
                {
                    app->systemRequestedQuit();
                    return MessageManager::getInstance()->hasStopMessageBeenSent();
                }
                return TRUE;
            case WM_POWERBROADCAST:
                handlePowerBroadcast (wParam);
                break;
            case WM_SYNCPAINT:
                return 0;
            case WM_DISPLAYCHANGE:
                InvalidateRect (h, nullptr, 0);
                // intentional fall-through...
                JUCE_FALLTHROUGH
            case WM_SETTINGCHANGE:  // note the fall-through in the previous case!
                doSettingChange();
                break;
            case WM_INITMENU:
                initialiseSysMenu ((HMENU) wParam);
                break;
            case WM_SYSCOMMAND:
                switch (wParam & 0xfff0)
                {
                case SC_CLOSE:
                    if (sendInputAttemptWhenModalMessage())
                        return 0;
                    if (hasTitleBar())
                    {
                        PostMessage (h, WM_CLOSE, 0, 0);
                        return 0;
                    }
                    break;
                case SC_KEYMENU:
                   #if ! JUCE_WINDOWS_ALT_KEY_TRIGGERS_MENU
                    // This test prevents a press of the ALT key from triggering the ancient top-left window menu.
                    // By default we suppress this behaviour because it's unlikely that more than a tiny subset of
                    // our users will actually want it, and it causes problems if you're trying to use the ALT key
                    // as a modifier for mouse actions. If you really need the old behaviour, then just define
                    // JUCE_WINDOWS_ALT_KEY_TRIGGERS_MENU=1 in your app.
                    if ((lParam >> 16) <= 0) // Values above zero indicate that a mouse-click triggered the menu
                        return 0;
                   #endif
                    // (NB mustn't call sendInputAttemptWhenModalMessage() here because of very obscure
                    // situations that can arise if a modal loop is started from an alt-key keypress).
                    if (hasTitleBar() && h == GetCapture())
                        ReleaseCapture();
                    break;
                case SC_MAXIMIZE:
                    if (! sendInputAttemptWhenModalMessage())
                        setFullScreen (true);
                    return 0;
                case SC_MINIMIZE:
                    if (sendInputAttemptWhenModalMessage())
                        return 0;
                    if (! hasTitleBar())
                    {
                        setMinimised (true);
                        return 0;
                    }
                    break;
                case SC_RESTORE:
                    if (sendInputAttemptWhenModalMessage())
                        return 0;
                    if (hasTitleBar())
                    {
                        if (isFullScreen())
                        {
                            setFullScreen (false);
                            return 0;
                        }
                    }
                    else
                    {
                        if (isMinimised())
                            setMinimised (false);
                        else if (isFullScreen())
                            setFullScreen (false);
                        return 0;
                    }
                    break;
                }
                break;
            case WM_NCPOINTERDOWN:
            case WM_NCLBUTTONDOWN:
                handleLeftClickInNCArea (wParam);
                break;
            case WM_NCRBUTTONDOWN:
            case WM_NCMBUTTONDOWN:
                sendInputAttemptWhenModalMessage();
                break;
            case WM_IME_SETCONTEXT:
                imeHandler.handleSetContext (h, wParam == TRUE);
                lParam &= ~(LPARAM) ISC_SHOWUICOMPOSITIONWINDOW;
                break;
            case WM_IME_STARTCOMPOSITION:  imeHandler.handleStartComposition (*this); return 0;
            case WM_IME_ENDCOMPOSITION:    imeHandler.handleEndComposition (*this, h); break;
            case WM_IME_COMPOSITION:       imeHandler.handleComposition (*this, h, lParam); return 0;
            case WM_GETDLGCODE:
                return DLGC_WANTALLKEYS;
            case WM_GETOBJECT:
            {
                if (static_cast<long> (lParam) == WindowsAccessibility::getUiaRootObjectId())
                {
                    if (auto* handler = component.getAccessibilityHandler())
                    {
                        LRESULT res = 0;
                        if (WindowsAccessibility::handleWmGetObject (handler, wParam, lParam, &res))
                        {
                            isAccessibilityActive = true;
                            return res;
                        }
                    }
                }
                break;
            }
            default:
                break;
        }
        return DefWindowProcW (h, message, wParam, lParam);
    }
    bool sendInputAttemptWhenModalMessage()
    {
        if (! component.isCurrentlyBlockedByAnotherModalComponent())
            return false;
        if (auto* current = Component::getCurrentlyModalComponent())
            current->inputAttemptWhenModal();
        return true;
    }
    //==============================================================================
    struct IMEHandler
    {
        IMEHandler()
        {
            reset();
        }
        void handleSetContext (HWND hWnd, const bool windowIsActive)
        {
            if (compositionInProgress && ! windowIsActive)
            {
                compositionInProgress = false;
                if (HIMC hImc = ImmGetContext (hWnd))
                {
                    ImmNotifyIME (hImc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
                    ImmReleaseContext (hWnd, hImc);
                }
            }
        }
        void handleStartComposition (ComponentPeer& owner)
        {
            reset();
            if (auto* target = owner.findCurrentTextInputTarget())
                target->insertTextAtCaret (String());
        }
        void handleEndComposition (ComponentPeer& owner, HWND hWnd)
        {
            if (compositionInProgress)
            {
                // If this occurs, the user has cancelled the composition, so clear their changes..
                if (auto* target = owner.findCurrentTextInputTarget())
                {
                    target->setHighlightedRegion (compositionRange);
                    target->insertTextAtCaret (String());
                    compositionRange.setLength (0);
                    target->setHighlightedRegion (Range<int>::emptyRange (compositionRange.getEnd()));
                    target->setTemporaryUnderlining ({});
                }
                if (auto hImc = ImmGetContext (hWnd))
                {
                    ImmNotifyIME (hImc, NI_CLOSECANDIDATE, 0, 0);
                    ImmReleaseContext (hWnd, hImc);
                }
            }
            reset();
        }
        void handleComposition (ComponentPeer& owner, HWND hWnd, const LPARAM lParam)
        {
            if (auto* target = owner.findCurrentTextInputTarget())
            {
                if (auto hImc = ImmGetContext (hWnd))
                {
                    if (compositionRange.getStart() < 0)
                        compositionRange = Range<int>::emptyRange (target->getHighlightedRegion().getStart());
                    if ((lParam & GCS_RESULTSTR) != 0) // (composition has finished)
                    {
                        replaceCurrentSelection (target, getCompositionString (hImc, GCS_RESULTSTR),
                                                 Range<int>::emptyRange (-1));
                        reset();
                        target->setTemporaryUnderlining ({});
                    }
                    else if ((lParam & GCS_COMPSTR) != 0) // (composition is still in-progress)
                    {
                        replaceCurrentSelection (target, getCompositionString (hImc, GCS_COMPSTR),
                                                 getCompositionSelection (hImc, lParam));
                        target->setTemporaryUnderlining (getCompositionUnderlines (hImc, lParam));
                        compositionInProgress = true;
                    }
                    moveCandidateWindowToLeftAlignWithSelection (hImc, owner, target);
                    ImmReleaseContext (hWnd, hImc);
                }
            }
        }
    private:
        //==============================================================================
        Range<int> compositionRange; // The range being modified in the TextInputTarget
        bool compositionInProgress;
        //==============================================================================
        void reset()
        {
            compositionRange = Range<int>::emptyRange (-1);
            compositionInProgress = false;
        }
        String getCompositionString (HIMC hImc, const DWORD type) const
        {
            jassert (hImc != HIMC{});
            const auto stringSizeBytes = ImmGetCompositionString (hImc, type, nullptr, 0);
            if (stringSizeBytes > 0)
            {
                HeapBlock<TCHAR> buffer;
                buffer.calloc ((size_t) stringSizeBytes / sizeof (TCHAR) + 1);
                ImmGetCompositionString (hImc, type, buffer, (DWORD) stringSizeBytes);
                return String (buffer.get());
            }
            return {};
        }
        int getCompositionCaretPos (HIMC hImc, LPARAM lParam, const String& currentIMEString) const
        {
            jassert (hImc != HIMC{});
            if ((lParam & CS_NOMOVECARET) != 0)
                return compositionRange.getStart();
            if ((lParam & GCS_CURSORPOS) != 0)
            {
                const int localCaretPos = ImmGetCompositionString (hImc, GCS_CURSORPOS, nullptr, 0);
                return compositionRange.getStart() + jmax (0, localCaretPos);
            }
            return compositionRange.getStart() + currentIMEString.length();
        }
        // Get selected/highlighted range while doing composition:
        // returned range is relative to beginning of TextInputTarget, not composition string
        Range<int> getCompositionSelection (HIMC hImc, LPARAM lParam) const
        {
            jassert (hImc != HIMC{});
            int selectionStart = 0;
            int selectionEnd = 0;
            if ((lParam & GCS_COMPATTR) != 0)
            {
                // Get size of attributes array:
                const int attributeSizeBytes = ImmGetCompositionString (hImc, GCS_COMPATTR, nullptr, 0);
                if (attributeSizeBytes > 0)
                {
                    // Get attributes (8 bit flag per character):
                    HeapBlock<char> attributes (attributeSizeBytes);
                    ImmGetCompositionString (hImc, GCS_COMPATTR, attributes, (DWORD) attributeSizeBytes);
                    selectionStart = 0;
                    for (selectionStart = 0; selectionStart < attributeSizeBytes; ++selectionStart)
                        if (attributes[selectionStart] == ATTR_TARGET_CONVERTED || attributes[selectionStart] == ATTR_TARGET_NOTCONVERTED)
                            break;
                    for (selectionEnd = selectionStart; selectionEnd < attributeSizeBytes; ++selectionEnd)
                        if (attributes[selectionEnd] != ATTR_TARGET_CONVERTED && attributes[selectionEnd] != ATTR_TARGET_NOTCONVERTED)
                            break;
                }
            }
            return Range<int> (selectionStart, selectionEnd) + compositionRange.getStart();
        }
        void replaceCurrentSelection (TextInputTarget* const target, const String& newContent, Range<int> newSelection)
        {
            if (compositionInProgress)
                target->setHighlightedRegion (compositionRange);
            target->insertTextAtCaret (newContent);
            compositionRange.setLength (newContent.length());
            if (newSelection.getStart() < 0)
                newSelection = Range<int>::emptyRange (compositionRange.getEnd());
            target->setHighlightedRegion (newSelection);
        }
        Array<Range<int>> getCompositionUnderlines (HIMC hImc, LPARAM lParam) const
        {
            Array<Range<int>> result;
            if (hImc != HIMC{} && (lParam & GCS_COMPCLAUSE) != 0)
            {
                auto clauseDataSizeBytes = ImmGetCompositionString (hImc, GCS_COMPCLAUSE, nullptr, 0);
                if (clauseDataSizeBytes > 0)
                {
                    const auto numItems = (size_t) clauseDataSizeBytes / sizeof (uint32);
                    HeapBlock<uint32> clauseData (numItems);
                    if (ImmGetCompositionString (hImc, GCS_COMPCLAUSE, clauseData, (DWORD) clauseDataSizeBytes) > 0)
                        for (size_t i = 0; i + 1 < numItems; ++i)
                            result.add (Range<int> ((int) clauseData[i], (int) clauseData[i + 1]) + compositionRange.getStart());
                }
            }
            return result;
        }
        void moveCandidateWindowToLeftAlignWithSelection (HIMC hImc, ComponentPeer& peer, TextInputTarget* target) const
        {
            if (auto* targetComp = dynamic_cast<Component*> (target))
            {
                auto area = peer.getComponent().getLocalArea (targetComp, target->getCaretRectangle());
                CANDIDATEFORM pos = { 0, CFS_CANDIDATEPOS, { area.getX(), area.getBottom() }, { 0, 0, 0, 0 } };
                ImmSetCandidateWindow (hImc, &pos);
            }
        }
        JUCE_DECLARE_NON_COPYABLE (IMEHandler)
    };
    void timerCallback() override
    {
        handlePositionChanged();
        stopTimer();
    }
    static bool isAncestor (HWND outer, HWND inner)
    {
        if (outer == nullptr || inner == nullptr)
            return false;
        if (outer == inner)
            return true;
        return isAncestor (outer, GetAncestor (inner, GA_PARENT));
    }
    void windowShouldDismissModals (HWND originator)
    {
        if (shouldIgnoreModalDismiss)
            return;
        if (isAncestor (originator, hwnd))
            sendInputAttemptWhenModalMessage();
    }
    // Unfortunately SetWindowsHookEx only allows us to register a static function as a hook.
    // To get around this, we keep a static list of listeners which are interested in
    // top-level window events, and notify all of these listeners from the callback.
    class TopLevelModalDismissBroadcaster
    {
    public:
        TopLevelModalDismissBroadcaster()
            : hook (SetWindowsHookEx (WH_CALLWNDPROC,
                                      callWndProc,
                                      (HINSTANCE) juce::Process::getCurrentModuleInstanceHandle(),
                                      GetCurrentThreadId()))
        {}
        ~TopLevelModalDismissBroadcaster() noexcept
        {
            UnhookWindowsHookEx (hook);
        }
    private:
        static void processMessage (int nCode, const CWPSTRUCT* info)
        {
            if (nCode < 0 || info == nullptr)
                return;
            constexpr UINT events[] { WM_MOVE,
                                      WM_SIZE,
                                      WM_WINDOWPOSCHANGING,
                                      WM_NCPOINTERDOWN,
                                      WM_NCLBUTTONDOWN,
                                      WM_NCRBUTTONDOWN,
                                      WM_NCMBUTTONDOWN };
            if (std::find (std::begin (events), std::end (events), info->message) == std::end (events))
                return;
            if (info->message == WM_WINDOWPOSCHANGING)
            {
                const auto* windowPos = reinterpret_cast<const WINDOWPOS*> (info->lParam);
                const auto windowPosFlags = windowPos->flags;
                constexpr auto maskToCheck = SWP_NOMOVE | SWP_NOSIZE;
                if ((windowPosFlags & maskToCheck) == maskToCheck)
                    return;
            }
            // windowMayDismissModals could affect the number of active ComponentPeer instances
            for (auto i = ComponentPeer::getNumPeers(); --i >= 0;)
                if (i < ComponentPeer::getNumPeers())
                    if (auto* hwndPeer = dynamic_cast<HWNDComponentPeer*> (ComponentPeer::getPeer (i)))
                        hwndPeer->windowShouldDismissModals (info->hwnd);
        }
        static LRESULT CALLBACK callWndProc (int nCode, WPARAM wParam, LPARAM lParam)
        {
            processMessage (nCode, reinterpret_cast<CWPSTRUCT*> (lParam));
            return CallNextHookEx ({}, nCode, wParam, lParam);
        }
        HHOOK hook;
    };
    SharedResourcePointer<TopLevelModalDismissBroadcaster> modalDismissBroadcaster;
    IMEHandler imeHandler;
    bool shouldIgnoreModalDismiss = false;
    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HWNDComponentPeer)
};
MultiTouchMapper<DWORD> HWNDComponentPeer::currentTouches;
ModifierKeys HWNDComponentPeer::modifiersAtLastCallback;
ComponentPeer* Component::createNewPeer (int styleFlags, void* parentHWND)
{
    return new HWNDComponentPeer (*this, styleFlags, (HWND) parentHWND, false);
}
JUCE_API ComponentPeer* createNonRepaintingEmbeddedWindowsPeer (Component& component, void* parentHWND)
{
    return new HWNDComponentPeer (component, ComponentPeer::windowIgnoresMouseClicks,
                                  (HWND) parentHWND, true);
}
JUCE_IMPLEMENT_SINGLETON (HWNDComponentPeer::WindowClassHolder)
//==============================================================================
bool KeyPress::isKeyCurrentlyDown (const int keyCode)
{
    auto k = (SHORT) keyCode;
    if ((keyCode & extendedKeyModifier) == 0)
    {
        if (k >= (SHORT) 'a' && k <= (SHORT) 'z')
            k += (SHORT) 'A' - (SHORT) 'a';
        // Only translate if extendedKeyModifier flag is not set
        const SHORT translatedValues[] = { (SHORT) ',', VK_OEM_COMMA,
                                           (SHORT) '+', VK_OEM_PLUS,
                                           (SHORT) '-', VK_OEM_MINUS,
                                           (SHORT) '.', VK_OEM_PERIOD,
                                           (SHORT) ';', VK_OEM_1,
                                           (SHORT) ':', VK_OEM_1,
                                           (SHORT) '/', VK_OEM_2,
                                           (SHORT) '?', VK_OEM_2,
                                           (SHORT) '[', VK_OEM_4,
                                           (SHORT) ']', VK_OEM_6 };
        for (int i = 0; i < numElementsInArray (translatedValues); i += 2)
            if (k == translatedValues[i])
                k = translatedValues[i + 1];
    }
    return HWNDComponentPeer::isKeyDown (k);
}
// (This internal function is used by the plugin client module)
bool offerKeyMessageToJUCEWindow (MSG& m)   { return HWNDComponentPeer::offerKeyMessageToJUCEWindow (m); }
//==============================================================================
static DWORD getProcess (HWND hwnd)
{
    DWORD result = 0;
    GetWindowThreadProcessId (hwnd, &result);
    return result;
}
/*  Returns true if the viewComponent is embedded into a window
    owned by the foreground process.
*/
bool isEmbeddedInForegroundProcess (Component* c)
{
    if (c == nullptr)
        return false;
    auto* peer = c->getPeer();
    auto* hwnd = peer != nullptr ? static_cast<HWND> (peer->getNativeHandle()) : nullptr;
    if (hwnd == nullptr)
        return true;
    const auto fgProcess    = getProcess (GetForegroundWindow());
    const auto ownerProcess = getProcess (GetAncestor (hwnd, GA_ROOTOWNER));
    return fgProcess == ownerProcess;
}
bool JUCE_CALLTYPE Process::isForegroundProcess()
{
    if (auto fg = GetForegroundWindow())
        return getProcess (fg) == GetCurrentProcessId();
    return true;
}
// N/A on Windows as far as I know.
void JUCE_CALLTYPE Process::makeForegroundProcess() {}
void JUCE_CALLTYPE Process::hide() {}
//==============================================================================
static BOOL CALLBACK enumAlwaysOnTopWindows (HWND hwnd, LPARAM lParam)
{
    if (IsWindowVisible (hwnd))
    {
        DWORD processID = 0;
        GetWindowThreadProcessId (hwnd, &processID);
        if (processID == GetCurrentProcessId())
        {
            WINDOWINFO info{};
            if (GetWindowInfo (hwnd, &info)
                 && (info.dwExStyle & WS_EX_TOPMOST) != 0)
            {
                *reinterpret_cast<bool*> (lParam) = true;
                return FALSE;
            }
        }
    }
    return TRUE;
}
bool juce_areThereAnyAlwaysOnTopWindows()
{
    bool anyAlwaysOnTopFound = false;
    EnumWindows (&enumAlwaysOnTopWindows, (LPARAM) &anyAlwaysOnTopFound);
    return anyAlwaysOnTopFound;
}
//==============================================================================
#if JUCE_MSVC
 // required to enable the newer dialog box on vista and above
 #pragma comment(linker,                             \
         "\"/MANIFESTDEPENDENCY:type='Win32' "       \
         "name='Microsoft.Windows.Common-Controls' " \
         "version='6.0.0.0' "                        \
         "processorArchitecture='*' "                \
         "publicKeyToken='6595b64144ccf1df' "        \
         "language='*'\""                            \
     )
#endif
class WindowsMessageBoxBase  : private AsyncUpdater
{
public:
    WindowsMessageBoxBase (Component* comp,
                           std::unique_ptr<ModalComponentManager::Callback>&& cb)
        : associatedComponent (comp),
          callback (std::move (cb))
    {
    }
    virtual int getResult() = 0;
    HWND getParentHWND() const
    {
        if (associatedComponent != nullptr)
            return (HWND) associatedComponent->getWindowHandle();
        return nullptr;
    }
    using AsyncUpdater::triggerAsyncUpdate;
private:
    void handleAsyncUpdate() override
    {
        const auto result = getResult();
        if (callback != nullptr)
            callback->modalStateFinished (result);
        delete this;
    }
    Component::SafePointer<Component> associatedComponent;
    std::unique_ptr<ModalComponentManager::Callback> callback;
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsMessageBoxBase)
};
class PreVistaMessageBox  : public WindowsMessageBoxBase
{
public:
    PreVistaMessageBox (const MessageBoxOptions& opts,
                        UINT extraFlags,
                        std::unique_ptr<ModalComponentManager::Callback>&& callback)
        : WindowsMessageBoxBase (opts.getAssociatedComponent(), std::move (callback)),
          flags (extraFlags | getMessageBoxFlags (opts.getIconType())),
          title (opts.getTitle()), message (opts.getMessage())
    {
    }
    int getResult() override
    {
        const auto result = MessageBox (getParentHWND(), message.toWideCharPointer(), title.toWideCharPointer(), flags);
        if (result == IDYES || result == IDOK)     return 0;
        if (result == IDNO && ((flags & 1) != 0))  return 1;
        return 2;
    }
private:
    static UINT getMessageBoxFlags (MessageBoxIconType iconType) noexcept
    {
        // this window can get lost behind JUCE windows which are set to be alwaysOnTop
        // so if there are any set it to be topmost
        const auto topmostFlag = juce_areThereAnyAlwaysOnTopWindows() ? MB_TOPMOST : 0;
        const auto iconFlags = [&]() -> decltype (topmostFlag)
        {
            switch (iconType)
            {
                case MessageBoxIconType::QuestionIcon:  return MB_ICONQUESTION;
                case MessageBoxIconType::WarningIcon:   return MB_ICONWARNING;
                case MessageBoxIconType::InfoIcon:      return MB_ICONINFORMATION;
                case MessageBoxIconType::NoIcon:        break;
            }
            return 0;
        }();
        return static_cast<UINT> (MB_TASKMODAL | MB_SETFOREGROUND | topmostFlag | iconFlags);
    }
    const UINT flags;
    const String title, message;
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PreVistaMessageBox)
};
using TaskDialogIndirectFunc = HRESULT (WINAPI*) (const TASKDIALOGCONFIG*, INT*, INT*, BOOL*);
static TaskDialogIndirectFunc taskDialogIndirect = nullptr;
class WindowsTaskDialog  : public WindowsMessageBoxBase
{
public:
    WindowsTaskDialog (const MessageBoxOptions& opts,
                       std::unique_ptr<ModalComponentManager::Callback>&& callback)
        : WindowsMessageBoxBase (opts.getAssociatedComponent(), std::move (callback)),
          iconType (opts.getIconType()),
          title (opts.getTitle()), message (opts.getMessage()),
          button1 (opts.getButtonText (0)), button2 (opts.getButtonText (1)), button3 (opts.getButtonText (2))
    {
    }
    int getResult() override
    {
        TASKDIALOGCONFIG config{};
        config.cbSize         = sizeof (config);
        config.pszWindowTitle = title.toWideCharPointer();
        config.pszContent     = message.toWideCharPointer();
        config.hInstance      = (HINSTANCE) Process::getCurrentModuleInstanceHandle();
        if (iconType == MessageBoxIconType::QuestionIcon)
        {
            if (auto* questionIcon = LoadIcon (nullptr, IDI_QUESTION))
            {
                config.hMainIcon = questionIcon;
                config.dwFlags |= TDF_USE_HICON_MAIN;
            }
        }
        else
        {
            auto icon = [this]() -> LPWSTR
            {
                switch (iconType)
                {
                    case MessageBoxIconType::WarningIcon:   return TD_WARNING_ICON;
                    case MessageBoxIconType::InfoIcon:      return TD_INFORMATION_ICON;
                    case MessageBoxIconType::QuestionIcon:  JUCE_FALLTHROUGH
                    case MessageBoxIconType::NoIcon:
                        break;
                }
                return nullptr;
            }();
            if (icon != nullptr)
                config.pszMainIcon = icon;
        }
        std::vector<TASKDIALOG_BUTTON> buttons;
        for (const auto* buttonText : { &button1, &button2, &button3 })
            if (buttonText->isNotEmpty())
                buttons.push_back ({ (int) buttons.size(), buttonText->toWideCharPointer() });
        config.pButtons = buttons.data();
        config.cButtons = (UINT) buttons.size();
        int buttonIndex = 0;
        taskDialogIndirect (&config, &buttonIndex, nullptr, nullptr);
        return buttonIndex;
    }
    static bool loadTaskDialog()
    {
        static bool hasChecked = false;
        if (! hasChecked)
        {
            hasChecked = true;
            const auto comctl = "Comctl32.dll";
            LoadLibraryA (comctl);
            const auto comctlModule = GetModuleHandleA (comctl);
            if (comctlModule != nullptr)
                taskDialogIndirect = (TaskDialogIndirectFunc) GetProcAddress (comctlModule, "TaskDialogIndirect");
        }
        return taskDialogIndirect != nullptr;
    }
private:
    MessageBoxIconType iconType;
    String title, message, button1, button2, button3;
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsTaskDialog)
};
static std::unique_ptr<WindowsMessageBoxBase> createMessageBox (const MessageBoxOptions& options,
                                                                std::unique_ptr<ModalComponentManager::Callback> callback)
{
    const auto useTaskDialog =
       #if JUCE_MODAL_LOOPS_PERMITTED
        callback != nullptr &&
       #endif
        SystemStats::getOperatingSystemType() >= SystemStats::WinVista
          && WindowsTaskDialog::loadTaskDialog();
    if (useTaskDialog)
        return std::make_unique<WindowsTaskDialog> (options, std::move (callback));
    const auto extraFlags = [&options]
    {
        const auto numButtons = options.getNumButtons();
        if (numButtons == 3)
            return MB_YESNOCANCEL;
        if (numButtons == 2)
            return options.getButtonText (0) == "OK" ? MB_OKCANCEL
                                                     : MB_YESNO;
        return MB_OK;
    }();
    return std::make_unique<PreVistaMessageBox> (options, (UINT) extraFlags, std::move (callback));
}
static int showDialog (const MessageBoxOptions& options,
                       ModalComponentManager::Callback* callbackIn,
                       AlertWindowMappings::MapFn mapFn)
{
   #if JUCE_MODAL_LOOPS_PERMITTED
    if (callbackIn == nullptr)
    {
        jassert (mapFn != nullptr);
        auto messageBox = createMessageBox (options, nullptr);
        return mapFn (messageBox->getResult());
    }
   #endif
    auto messageBox = createMessageBox (options,
                                        AlertWindowMappings::getWrappedCallback (callbackIn, mapFn));
    messageBox->triggerAsyncUpdate();
    messageBox.release();
    return 0;
}
#if JUCE_MODAL_LOOPS_PERMITTED
void JUCE_CALLTYPE NativeMessageBox::showMessageBox (MessageBoxIconType iconType,
                                                     const String& title, const String& message,
                                                     Component* associatedComponent)
{
    showDialog (MessageBoxOptions()
                  .withIconType (iconType)
                  .withTitle (title)
                  .withMessage (message)
                  .withButton (TRANS("OK"))
                  .withAssociatedComponent (associatedComponent),
                nullptr, AlertWindowMappings::messageBox);
}
int JUCE_CALLTYPE NativeMessageBox::show (const MessageBoxOptions& options)
{
    return showDialog (options, nullptr, AlertWindowMappings::noMapping);
}
#endif
void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (MessageBoxIconType iconType,
                                                          const String& title, const String& message,
                                                          Component* associatedComponent,
                                                          ModalComponentManager::Callback* callback)
{
    showDialog (MessageBoxOptions()
                  .withIconType (iconType)
                  .withTitle (title)
                  .withMessage (message)
                  .withButton (TRANS("OK"))
                  .withAssociatedComponent (associatedComponent),
                callback, AlertWindowMappings::messageBox);
}
bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (MessageBoxIconType iconType,
                                                      const String& title, const String& message,
                                                      Component* associatedComponent,
                                                      ModalComponentManager::Callback* callback)
{
    return showDialog (MessageBoxOptions()
                         .withIconType (iconType)
                         .withTitle (title)
                         .withMessage (message)
                         .withButton (TRANS("OK"))
                         .withButton (TRANS("Cancel"))
                         .withAssociatedComponent (associatedComponent),
                       callback, AlertWindowMappings::okCancel) != 0;
}
int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (MessageBoxIconType iconType,
                                                        const String& title, const String& message,
                                                        Component* associatedComponent,
                                                        ModalComponentManager::Callback* callback)
{
    return showDialog (MessageBoxOptions()
                         .withIconType (iconType)
                         .withTitle (title)
                         .withMessage (message)
                         .withButton (TRANS("Yes"))
                         .withButton (TRANS("No"))
                         .withButton (TRANS("Cancel"))
                         .withAssociatedComponent (associatedComponent),
                       callback, AlertWindowMappings::yesNoCancel);
}
int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (MessageBoxIconType iconType,
                                                  const String& title, const String& message,
                                                  Component* associatedComponent,
                                                  ModalComponentManager::Callback* callback)
{
    return showDialog (MessageBoxOptions()
                         .withIconType (iconType)
                         .withTitle (title)
                         .withMessage (message)
                         .withButton (TRANS("Yes"))
                         .withButton (TRANS("No"))
                         .withAssociatedComponent (associatedComponent),
                       callback, AlertWindowMappings::okCancel);
}
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
                                                ModalComponentManager::Callback* callback)
{
    showDialog (options, callback, AlertWindowMappings::noMapping);
}
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
                                                std::function<void (int)> callback)
{
    showAsync (options, ModalCallbackFunction::create (callback));
}
//==============================================================================
bool MouseInputSource::SourceList::addSource()
{
    auto numSources = sources.size();
    if (numSources == 0 || canUseMultiTouch())
    {
        addSource (numSources, numSources == 0 ? MouseInputSource::InputSourceType::mouse
                                               : MouseInputSource::InputSourceType::touch);
        return true;
    }
    return false;
}
bool MouseInputSource::SourceList::canUseTouch()
{
    return canUseMultiTouch();
}
Point<float> MouseInputSource::getCurrentRawMousePosition()
{
    POINT mousePos;
    GetCursorPos (&mousePos);
    auto p = pointFromPOINT (mousePos);
    if (isPerMonitorDPIAwareThread())
        p = Desktop::getInstance().getDisplays().physicalToLogical (p);
    return p.toFloat();
}
void MouseInputSource::setRawMousePosition (Point<float> newPosition)
{
    auto newPositionInt = newPosition.roundToInt();
   #if JUCE_WIN_PER_MONITOR_DPI_AWARE
    if (isPerMonitorDPIAwareThread())
        newPositionInt = Desktop::getInstance().getDisplays().logicalToPhysical (newPositionInt);
   #endif
    auto point = POINTFromPoint (newPositionInt);
    SetCursorPos (point.x, point.y);
}
//==============================================================================
class ScreenSaverDefeater   : public Timer
{
public:
    ScreenSaverDefeater()
    {
        startTimer (10000);
        timerCallback();
    }
    void timerCallback() override
    {
        if (Process::isForegroundProcess())
        {
            INPUT input = {};
            input.type = INPUT_MOUSE;
            input.mi.mouseData = MOUSEEVENTF_MOVE;
            SendInput (1, &input, sizeof (INPUT));
        }
    }
};
static std::unique_ptr<ScreenSaverDefeater> screenSaverDefeater;
void Desktop::setScreenSaverEnabled (const bool isEnabled)
{
    if (isEnabled)
        screenSaverDefeater = nullptr;
    else if (screenSaverDefeater == nullptr)
        screenSaverDefeater.reset (new ScreenSaverDefeater());
}
bool Desktop::isScreenSaverEnabled()
{
    return screenSaverDefeater == nullptr;
}
//==============================================================================
void LookAndFeel::playAlertSound()
{
    MessageBeep (MB_OK);
}
//==============================================================================
void SystemClipboard::copyTextToClipboard (const String& text)
{
    if (OpenClipboard (nullptr) != 0)
    {
        if (EmptyClipboard() != 0)
        {
            auto bytesNeeded = CharPointer_UTF16::getBytesRequiredFor (text.getCharPointer()) + 4;
            if (bytesNeeded > 0)
            {
                if (auto bufH = GlobalAlloc (GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT, bytesNeeded + sizeof (WCHAR)))
                {
                    if (auto* data = static_cast<WCHAR*> (GlobalLock (bufH)))
                    {
                        text.copyToUTF16 (data, bytesNeeded);
                        GlobalUnlock (bufH);
                        SetClipboardData (CF_UNICODETEXT, bufH);
                    }
                }
            }
        }
        CloseClipboard();
    }
}
String SystemClipboard::getTextFromClipboard()
{
    String result;
    if (OpenClipboard (nullptr) != 0)
    {
        if (auto bufH = GetClipboardData (CF_UNICODETEXT))
        {
            if (auto* data = (const WCHAR*) GlobalLock (bufH))
            {
                result = String (data, (size_t) (GlobalSize (bufH) / sizeof (WCHAR)));
                GlobalUnlock (bufH);
            }
        }
        CloseClipboard();
    }
    return result;
}
//==============================================================================
void Desktop::setKioskComponent (Component* kioskModeComp, bool enableOrDisable, bool /*allowMenusAndBars*/)
{
    if (auto* tlw = dynamic_cast<TopLevelWindow*> (kioskModeComp))
        tlw->setUsingNativeTitleBar (! enableOrDisable);
    if (kioskModeComp != nullptr && enableOrDisable)
        kioskModeComp->setBounds (getDisplays().getDisplayForRect (kioskModeComp->getScreenBounds())->totalArea);
}
void Desktop::allowedOrientationsChanged() {}
//==============================================================================
static const Displays::Display* getCurrentDisplayFromScaleFactor (HWND hwnd)
{
    Array<const Displays::Display*> candidateDisplays;
    const auto scaleToLookFor = [&]
    {
        if (auto* peer = HWNDComponentPeer::getOwnerOfWindow (hwnd))
            return peer->getPlatformScaleFactor();
        return getScaleFactorForWindow (hwnd);
    }();
    auto globalScale = Desktop::getInstance().getGlobalScaleFactor();
    for (auto& d : Desktop::getInstance().getDisplays().displays)
        if (approximatelyEqual (d.scale / globalScale, scaleToLookFor))
            candidateDisplays.add (&d);
    if (candidateDisplays.size() > 0)
    {
        if (candidateDisplays.size() == 1)
            return candidateDisplays[0];
        const auto bounds = [&]
        {
            if (auto* peer = HWNDComponentPeer::getOwnerOfWindow (hwnd))
                return peer->getComponent().getTopLevelComponent()->getBounds();
            return Desktop::getInstance().getDisplays().physicalToLogical (rectangleFromRECT (getWindowScreenRect (hwnd)));
        }();
        const Displays::Display* retVal = nullptr;
        int maxArea = -1;
        for (auto* d : candidateDisplays)
        {
            auto intersection = d->totalArea.getIntersection (bounds);
            auto area = intersection.getWidth() * intersection.getHeight();
            if (area > maxArea)
            {
                maxArea = area;
                retVal = d;
            }
        }
        if (retVal != nullptr)
            return retVal;
    }
    return Desktop::getInstance().getDisplays().getPrimaryDisplay();
}
//==============================================================================
struct MonitorInfo
{
    MonitorInfo (bool main, RECT totalArea, RECT workArea, double d) noexcept
        : isMain (main),
          totalAreaRect (totalArea),
          workAreaRect (workArea),
          dpi (d)
    {
    }
    bool isMain;
    RECT totalAreaRect, workAreaRect;
    double dpi;
};
static BOOL CALLBACK enumMonitorsProc (HMONITOR hm, HDC, LPRECT, LPARAM userInfo)
{
    MONITORINFO info = {};
    info.cbSize = sizeof (info);
    GetMonitorInfo (hm, &info);
    auto isMain = (info.dwFlags & 1 /* MONITORINFOF_PRIMARY */) != 0;
    auto dpi = 0.0;
    if (getDPIForMonitor != nullptr)
    {
        UINT dpiX = 0, dpiY = 0;
        if (SUCCEEDED (getDPIForMonitor (hm, MDT_Default, &dpiX, &dpiY)))
            dpi = (dpiX + dpiY) / 2.0;
    }
    ((Array<MonitorInfo>*) userInfo)->add ({ isMain, info.rcMonitor, info.rcWork, dpi });
    return TRUE;
}
void Displays::findDisplays (float masterScale)
{
    setDPIAwareness();
    Array<MonitorInfo> monitors;
    EnumDisplayMonitors (nullptr, nullptr, &enumMonitorsProc, (LPARAM) &monitors);
    auto globalDPI = getGlobalDPI();
    if (monitors.size() == 0)
    {
        auto windowRect = getWindowScreenRect (GetDesktopWindow());
        monitors.add ({ true, windowRect, windowRect, globalDPI });
    }
    // make sure the first in the list is the main monitor
    for (int i = 1; i < monitors.size(); ++i)
        if (monitors.getReference (i).isMain)
            monitors.swap (i, 0);
    for (auto& monitor : monitors)
    {
        Display d;
        d.isMain = monitor.isMain;
        d.dpi = monitor.dpi;
        if (d.dpi == 0)
        {
            d.dpi = globalDPI;
            d.scale = masterScale;
        }
        else
        {
            d.scale = (d.dpi / USER_DEFAULT_SCREEN_DPI) * (masterScale / Desktop::getDefaultMasterScale());
        }
        d.totalArea = rectangleFromRECT (monitor.totalAreaRect);
        d.userArea  = rectangleFromRECT (monitor.workAreaRect);
        displays.add (d);
    }
   #if JUCE_WIN_PER_MONITOR_DPI_AWARE
    if (isPerMonitorDPIAwareThread())
        updateToLogical();
    else
   #endif
    {
        for (auto& d : displays)
        {
            d.totalArea /= masterScale;
            d.userArea  /= masterScale;
        }
    }
}
//==============================================================================
static HICON extractFileHICON (const File& file)
{
    WORD iconNum = 0;
    WCHAR name[MAX_PATH * 2];
    file.getFullPathName().copyToUTF16 (name, sizeof (name));
    return ExtractAssociatedIcon ((HINSTANCE) Process::getCurrentModuleInstanceHandle(),
                                  name, &iconNum);
}
Image juce_createIconForFile (const File& file)
{
    Image image;
    if (auto icon = extractFileHICON (file))
    {
        image = IconConverters::createImageFromHICON (icon);
        DestroyIcon (icon);
    }
    return image;
}
//==============================================================================
class MouseCursor::PlatformSpecificHandle
{
public:
    explicit PlatformSpecificHandle (const MouseCursor::StandardCursorType type)
        : impl (makeHandle (type)) {}
    explicit PlatformSpecificHandle (const CustomMouseCursorInfo& info)
        : impl (makeHandle (info)) {}
    static void showInWindow (PlatformSpecificHandle* handle, ComponentPeer* peer)
    {
        SetCursor ([&]
        {
            if (handle != nullptr && handle->impl != nullptr && peer != nullptr)
                return handle->impl->getCursor (*peer);
            return LoadCursor (nullptr, IDC_ARROW);
        }());
    }
private:
    struct Impl
    {
        virtual ~Impl() = default;
        virtual HCURSOR getCursor (ComponentPeer&) = 0;
    };
    class BuiltinImpl : public Impl
    {
    public:
        explicit BuiltinImpl (HCURSOR cursorIn)
            : cursor (cursorIn) {}
        HCURSOR getCursor (ComponentPeer&) override { return cursor; }
    private:
        HCURSOR cursor;
    };
    class ImageImpl : public Impl
    {
    public:
        explicit ImageImpl (const CustomMouseCursorInfo& infoIn) : info (infoIn) {}
        ~ImageImpl() override
        {
            for (auto& pair : cursorsBySize)
                DestroyCursor (pair.second);
        }
        HCURSOR getCursor (ComponentPeer& peer) override
        {
            JUCE_ASSERT_MESSAGE_THREAD;
            static auto getCursorSize = getCursorSizeForPeerFunction();
            const auto size = getCursorSize (peer);
            const auto iter = cursorsBySize.find (size);
            if (iter != cursorsBySize.end())
                return iter->second;
            const auto imgW = jmax (1, info.image.getWidth());
            const auto imgH = jmax (1, info.image.getHeight());
            const auto scale = (float) size / (float) unityCursorSize;
            const auto scaleToUse = scale * jmin (1.0f, jmin ((float) unityCursorSize / (float) imgW,
                                                              (float) unityCursorSize / (float) imgH)) / info.scaleFactor;
            const auto rescaled = info.image.rescaled (roundToInt (scaleToUse * (float) imgW),
                                                       roundToInt (scaleToUse * (float) imgH));
            const auto hx = jlimit (0, rescaled.getWidth(),  roundToInt (scaleToUse * (float) info.hotspot.x));
            const auto hy = jlimit (0, rescaled.getHeight(), roundToInt (scaleToUse * (float) info.hotspot.y));
            return cursorsBySize.emplace (size, IconConverters::createHICONFromImage (rescaled, false, hx, hy)).first->second;
        }
    private:
        const CustomMouseCursorInfo info;
        std::map<int, HCURSOR> cursorsBySize;
    };
    static auto getCursorSizeForPeerFunction() -> int (*) (ComponentPeer&)
    {
        static const auto getDpiForMonitor = []() -> GetDPIForMonitorFunc
        {
            constexpr auto library = "SHCore.dll";
            LoadLibraryA (library);
            if (auto* handle = GetModuleHandleA (library))
                return (GetDPIForMonitorFunc) GetProcAddress (handle, "GetDpiForMonitor");
            return nullptr;
        }();
        static const auto getSystemMetricsForDpi = []() -> GetSystemMetricsForDpiFunc
        {
            constexpr auto library = "User32.dll";
            LoadLibraryA (library);
            if (auto* handle = GetModuleHandleA (library))
                return (GetSystemMetricsForDpiFunc) GetProcAddress (handle, "GetSystemMetricsForDpi");
            return nullptr;
        }();
        if (getDpiForMonitor == nullptr || getSystemMetricsForDpi == nullptr)
            return [] (ComponentPeer&) { return unityCursorSize; };
        return [] (ComponentPeer& p)
        {
            const ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { p.getNativeHandle() };
            UINT dpiX = 0, dpiY = 0;
            if (auto* monitor = MonitorFromWindow ((HWND) p.getNativeHandle(), MONITOR_DEFAULTTONULL))
                if (SUCCEEDED (getDpiForMonitor (monitor, MDT_Default, &dpiX, &dpiY)))
                    return getSystemMetricsForDpi (SM_CXCURSOR, dpiX);
            return unityCursorSize;
        };
    }
    static constexpr auto unityCursorSize = 32;
    static std::unique_ptr<Impl> makeHandle (const CustomMouseCursorInfo& info)
    {
        return std::make_unique<ImageImpl> (info);
    }
    static std::unique_ptr<Impl> makeHandle (const MouseCursor::StandardCursorType type)
    {
        LPCTSTR cursorName = IDC_ARROW;
        switch (type)
        {
            case NormalCursor:
            case ParentCursor:                  break;
            case NoCursor:                      return std::make_unique<BuiltinImpl> (nullptr);
            case WaitCursor:                    cursorName = IDC_WAIT; break;
            case IBeamCursor:                   cursorName = IDC_IBEAM; break;
            case PointingHandCursor:            cursorName = MAKEINTRESOURCE(32649); break;
            case CrosshairCursor:               cursorName = IDC_CROSS; break;
            case LeftRightResizeCursor:
            case LeftEdgeResizeCursor:
            case RightEdgeResizeCursor:         cursorName = IDC_SIZEWE; break;
            case UpDownResizeCursor:
            case TopEdgeResizeCursor:
            case BottomEdgeResizeCursor:        cursorName = IDC_SIZENS; break;
            case TopLeftCornerResizeCursor:
            case BottomRightCornerResizeCursor: cursorName = IDC_SIZENWSE; break;
            case TopRightCornerResizeCursor:
            case BottomLeftCornerResizeCursor:  cursorName = IDC_SIZENESW; break;
            case UpDownLeftRightResizeCursor:   cursorName = IDC_SIZEALL; break;
            case DraggingHandCursor:
            {
                static const unsigned char dragHandData[]
                    { 71,73,70,56,57,97,16,0,16,0,145,2,0,0,0,0,255,255,255,0,0,0,0,0,0,33,249,4,1,0,0,2,0,44,0,0,0,0,16,0,
                      16,0,0,2,52,148,47,0,200,185,16,130,90,12,74,139,107,84,123,39,132,117,151,116,132,146,248,60,209,138,
                      98,22,203,114,34,236,37,52,77,217,247,154,191,119,110,240,193,128,193,95,163,56,60,234,98,135,2,0,59 };
                return makeHandle ({ ImageFileFormat::loadFrom (dragHandData, sizeof (dragHandData)), { 8, 7 } });
            }
            case CopyingCursor:
            {
                static const unsigned char copyCursorData[]
                    { 71,73,70,56,57,97,21,0,21,0,145,0,0,0,0,0,255,255,255,0,128,128,255,255,255,33,249,4,1,0,0,3,0,44,0,0,0,0,21,0,
                      21,0,0,2,72,4,134,169,171,16,199,98,11,79,90,71,161,93,56,111,78,133,218,215,137,31,82,154,100,200,86,91,202,142,
                      12,108,212,87,235,174, 15,54,214,126,237,226,37,96,59,141,16,37,18,201,142,157,230,204,51,112,252,114,147,74,83,
                      5,50,68,147,208,217,16,71,149,252,124,5,0,59,0,0 };
                return makeHandle ({ ImageFileFormat::loadFrom (copyCursorData, sizeof (copyCursorData)), { 1, 3 } });
            }
            case NumStandardCursorTypes: JUCE_FALLTHROUGH
            default:
                jassertfalse; break;
        }
        return std::make_unique<BuiltinImpl> ([&]
        {
            if (auto* c = LoadCursor (nullptr, cursorName))
                return c;
            return LoadCursor (nullptr, IDC_ARROW);
        }());
    }
    std::unique_ptr<Impl> impl;
};
//==============================================================================
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
} // namespace juce
 |