/* ============================================================================== 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 #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 ((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 rectangleFromRECT (RECT r) noexcept { return { r.left, r.top, r.right - r.left, r.bottom - r.top }; } static RECT RECTFromRectangle (Rectangle r) noexcept { return { r.getX(), r.getY(), r.getRight(), r.getBottom() }; } static Point pointFromPOINT (POINT p) noexcept { return { p.x, p.y }; } static POINT POINTFromPoint (Point p) noexcept { return { p.x, p.y }; } //============================================================================== static const Displays::Display* getCurrentDisplayFromScaleFactor (HWND hwnd); template static Rectangle convertPhysicalScreenRectangleToLogical (Rectangle r, HWND h) noexcept { if (isPerMonitorDPIAwareWindow (h)) return Desktop::getInstance().getDisplays().physicalToLogical (r, getCurrentDisplayFromScaleFactor (h)); return r; } template static Rectangle convertLogicalScreenRectangleToPhysical (Rectangle r, HWND h) noexcept { if (isPerMonitorDPIAwareWindow (h)) return Desktop::getInstance().getDisplays().logicalToPhysical (r, getCurrentDisplayFromScaleFactor (h)); return r; } static Point convertPhysicalScreenPointToLogical (Point p, HWND h) noexcept { if (isPerMonitorDPIAwareWindow (h)) return Desktop::getInstance().getDisplays().physicalToLogical (p, getCurrentDisplayFromScaleFactor (h)); return p; } static Point convertLogicalScreenPointToPhysical (Point 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 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 } ~NativeDarkModeChangeDetectorImpl() { UnhookWindowsHookEx (hook); } 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 (lParam); if (nCode >= 0 && params != nullptr && params->message == WM_SETTINGCHANGE && params->lParam != 0 && CompareStringOrdinal (reinterpret_cast (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::createNativeDarkModeChangeDetectorImpl() { return std::make_unique(); } 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 createType() const override { return std::make_unique(); } std::unique_ptr createLowLevelContext() override { sendDataChangeMessage(); return std::make_unique (Image (this)); } void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override { const auto offset = (size_t) (x * pixelStride + y * lineStride); bitmap.data = imageData + offset; bitmap.size = (size_t) (lineStride * height) - offset; 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)) { 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; header.bV5CSType = 0x57696E20; // 'Win ' header.bV5Intent = LCS_GM_IMAGES; 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 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; }; } // namespace juce #ifdef __CRT_UUID_DECL __CRT_UUID_DECL (juce::ITipInvocation, 0x37c994e7, 0x432b, 0x4834, 0xa2, 0xf7, 0xdc, 0xe1, 0xf1, 0x3b, 0x83, 0x4b) #endif namespace juce { 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 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 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; }; } // namespace juce #ifdef __CRT_UUID_DECL __CRT_UUID_DECL (juce::IUIViewSettingsInterop, 0x3694dbf9, 0x8f68, 0x44be, 0x8f, 0xf5, 0x19, 0x5c, 0x98, 0xed, 0xe8, 0xa6) __CRT_UUID_DECL (juce::IUIViewSettings, 0xc63657f6, 0x8850, 0x470d, 0x88, 0xf8, 0x45, 0x5e, 0x16, 0xea, 0x2c, 0x26) #endif namespace juce { 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 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() = default; 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 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 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 (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& bounds, bool isNowFullScreen) override { // If we try to set new bounds while handling an existing position change, // Windows may get confused about our current scale and size. // This can happen when moving a window between displays, because the mouse-move // generator in handlePositionChanged can cause the window to move again. if (inHandlePositionChanged) return; const ScopedValueSetter 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, ! inDpiChange); if (hasResized && isValidPeer (this)) { updateBorderSize(); repaintNowIfTransparent(); } } Rectangle 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 getScreenPosition() const { auto r = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (hwnd)), hwnd); return { r.getX() + windowBorder.getLeft(), r.getY() + windowBorder.getTop() }; } Point localToGlobal (Point relativePosition) override { return relativePosition + getScreenPosition().toFloat(); } Point globalToLocal (Point screenPosition) override { return screenPosition - getScreenPosition().toFloat(); } using ComponentPeer::localToGlobal; using ComponentPeer::globalToLocal; void setAlpha (float newAlpha) override { const ScopedValueSetter 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 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 scope (shouldIgnoreModalDismiss, true); setMinimised (false); if (isFullScreen() != shouldBeFullScreen) { if (constrainer != nullptr) constrainer->resizeStart(); fullScreen = shouldBeFullScreen; const WeakReference 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 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)); } OptionalBorderSize getFrameSizeIfPresent() const override { return ComponentPeer::OptionalBorderSize { windowBorder }; } BorderSize 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 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 scope (shouldIgnoreModalDismiss, true); if (auto* otherPeer = dynamic_cast (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 scope (shouldIgnoreModalDismiss, true); const bool oldDeactivate = shouldDeactivateTitleBar; shouldDeactivateTitleBar = ((styleFlags & windowIsTemporary) == 0); callFunctionIfNotLocked (&setFocusCallback, hwnd); shouldDeactivateTitleBar = oldDeactivate; } void textInputRequired (Point, 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& area) override { auto r = RECTFromRectangle ((area.toDouble() * getPlatformScaleFactor()).getSmallestIntegerContainer()); InvalidateRect (hwnd, &r, FALSE); } void performAnyPendingRepaintsNow() override { if (component.isVisible()) { WeakReference 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 { 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 getMousePos (POINTL mousePos) const { const auto originalPos = pointFromPOINT ({ mousePos.x, mousePos.y }); const auto logicalPos = convertPhysicalScreenPointToLogical (originalPos, peer.hwnd); return ScalingHelpers::screenPosToLocalPos (peer.component, logicalPos.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 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 (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 shadower; RenderingEngineType currentRenderingEngine; #if JUCE_DIRECT2D std::unique_ptr direct2DContext; #endif uint32 lastPaintTime = 0; ULONGLONG lastMagnifySize = 0; bool fullScreen = false, isDragging = false, isMouseOver = false, hasCreatedCaret = false, constrainerIsResizing = false; BorderSize 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; bool inDpiChange = 0, inHandlePositionChanged = 0; bool isAccessibilityActive = false; //============================================================================== static MultiTouchMapper 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 (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) { if ((styleFlags & windowIsOwned) == 0) 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 { TCHAR messageBuffer[256] = {}; FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, GetLastError(), MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), messageBuffer, (DWORD) numElementsInArray (messageBuffer) - 1, nullptr); DBG (messageBuffer); jassertfalse; } } static BOOL CALLBACK revokeChildDragDropCallback (HWND hwnd, LPARAM) { RevokeDragDrop (hwnd); return TRUE; } static void* destroyWindowCallback (void* handle) { auto hwnd = reinterpret_cast (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 = 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* clip; Point 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 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 contextClip; const Rectangle 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 ((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 (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 (w, h)); } ChildWindowClippingInfo childClipInfo = { dc, this, &contextClip, Point (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 (offscreenImage.getPixelData()) ->blitToWindow (hwnd, dc, transparent, x, y, updateLayeredWindowAlpha); } if (childClipInfo.savedDC != 0) RestoreDC (dc, childClipInfo.savedDC); } } //============================================================================== void doMouseEvent (Point 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 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::defaultPressure, MouseInputSource::defaultOrientation, modsToSend); } } void doMouseDown (Point 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::defaultPressure); } } void doMouseUp (Point 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::defaultPressure); } 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::defaultPressure); } ComponentPeer* findPeerUnderMouse (Point& 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 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 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 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::defaultPressure, 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 (touchInfo.pressure) : MouseInputSource::defaultPressure; const auto orientation = touchInfo.touchMask & TOUCH_MASK_ORIENTATION ? degreesToRadians (static_cast (touchInfo.orientation)) : MouseInputSource::defaultOrientation; 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::defaultPressure; 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 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 (penInfo.rotation)) : MouseInputSource::defaultRotation; penDetails.tiltX = (penInfo.penMask & PEN_MASK_TILT_X) ? (float) penInfo.tiltX / 90.0f : MouseInputSource::defaultTiltX; penDetails.tiltY = (penInfo.penMask & PEN_MASK_TILT_Y) ? (float) penInfo.tiltY / 90.0f : MouseInputSource::defaultTiltY; 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::defaultOrientation, 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::defaultOrientation, 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::defaultOrientation, 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 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)) { const ScopedValueSetter scope (inHandlePositionChanged, true); if (! areOtherTouchSourcesActive()) doMouseEvent (pos, MouseInputSource::defaultPressure); 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 setter (inDpiChange, true); SetWindowPos (hwnd, nullptr, newRect.left, newRect.top, newRect.right - newRect.left, newRect.bottom - newRect.top, SWP_NOZORDER | SWP_NOACTIVATE); } // 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 (inDpiChange) { // Danger! Re-entrant call to handleDPIChanging. // Please report this issue on the JUCE forum, along with instructions // so that a JUCE developer can reproduce the issue. jassertfalse; 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 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 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 (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 (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()) if (auto* owner = getOwnerOfWindow ((HWND) current->getWindowHandle())) if (! owner->shouldIgnoreModalDismiss) 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::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::emptyRange (target->getHighlightedRegion().getStart()); if ((lParam & GCS_RESULTSTR) != 0) // (composition has finished) { replaceCurrentSelection (target, getCompositionString (hImc, GCS_RESULTSTR), Range::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 compositionRange; // The range being modified in the TextInputTarget bool compositionInProgress; //============================================================================== void reset() { compositionRange = Range::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 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 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 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 (selectionStart, selectionEnd) + compositionRange.getStart(); } void replaceCurrentSelection (TextInputTarget* const target, const String& newContent, Range newSelection) { if (compositionInProgress) target->setHighlightedRegion (compositionRange); target->insertTextAtCaret (newContent); compositionRange.setLength (newContent.length()); if (newSelection.getStart() < 0) newSelection = Range::emptyRange (compositionRange.getEnd()); target->setHighlightedRegion (newSelection); } Array> getCompositionUnderlines (HIMC hImc, LPARAM lParam) const { Array> 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 clauseData (numItems); if (ImmGetCompositionString (hImc, GCS_COMPCLAUSE, clauseData, (DWORD) clauseDataSizeBytes) > 0) for (size_t i = 0; i + 1 < numItems; ++i) result.add (Range ((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 (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 (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 (ComponentPeer::getPeer (i))) hwndPeer->windowShouldDismissModals (info->hwnd); } static LRESULT CALLBACK callWndProc (int nCode, WPARAM wParam, LPARAM lParam) { processMessage (nCode, reinterpret_cast (lParam)); return CallNextHookEx ({}, nCode, wParam, lParam); } HHOOK hook; }; SharedResourcePointer modalDismissBroadcaster; IMEHandler imeHandler; bool shouldIgnoreModalDismiss = false; //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HWNDComponentPeer) }; MultiTouchMapper 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 (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 (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&& 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 associatedComponent; std::unique_ptr callback; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsMessageBoxBase) }; class PreVistaMessageBox : public WindowsMessageBoxBase { public: PreVistaMessageBox (const MessageBoxOptions& opts, UINT extraFlags, std::unique_ptr&& cb) : WindowsMessageBoxBase (opts.getAssociatedComponent(), std::move (cb)), 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 (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&& cb) : WindowsMessageBoxBase (opts.getAssociatedComponent(), std::move (cb)), 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.hwndParent = getParentHWND(); 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 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 createMessageBox (const MessageBoxOptions& options, std::unique_ptr callback) { const auto useTaskDialog = #if JUCE_MODAL_LOOPS_PERMITTED callback != nullptr && #endif SystemStats::getOperatingSystemType() >= SystemStats::WinVista && WindowsTaskDialog::loadTaskDialog(); if (useTaskDialog) return std::make_unique (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 (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 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 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 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; 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 (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 (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 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*) userInfo)->add ({ isMain, info.rcMonitor, info.rcWork, dpi }); return TRUE; } void Displays::findDisplays (float masterScale) { setDPIAwareness(); Array 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 logicalSize = info.image.getScaledBounds(); const auto scale = (float) size / (float) unityCursorSize; const auto physicalSize = logicalSize * scale; const auto& image = info.image.getImage(); const auto rescaled = image.rescaled (roundToInt ((float) physicalSize.getWidth()), roundToInt ((float) physicalSize.getHeight())); const auto effectiveScale = rescaled.getWidth() / logicalSize.getWidth(); const auto hx = jlimit (0, rescaled.getWidth(), roundToInt ((float) info.hotspot.x * effectiveScale)); const auto hy = jlimit (0, rescaled.getHeight(), roundToInt ((float) info.hotspot.y * effectiveScale)); return cursorsBySize.emplace (size, IconConverters::createHICONFromImage (rescaled, false, hx, hy)).first->second; } private: const CustomMouseCursorInfo info; std::map 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 makeHandle (const CustomMouseCursorInfo& info) { return std::make_unique (info); } static std::unique_ptr makeHandle (const MouseCursor::StandardCursorType type) { LPCTSTR cursorName = IDC_ARROW; switch (type) { case NormalCursor: case ParentCursor: break; case NoCursor: return std::make_unique (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 ({ ScaledImage (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 ({ ScaledImage (ImageFileFormat::loadFrom (copyCursorData, sizeof (copyCursorData))), { 1, 3 } }); } case NumStandardCursorTypes: JUCE_FALLTHROUGH default: jassertfalse; break; } return std::make_unique ([&] { if (auto* c = LoadCursor (nullptr, cursorName)) return c; return LoadCursor (nullptr, IDC_ARROW); }()); } std::unique_ptr impl; }; //============================================================================== JUCE_END_IGNORE_WARNINGS_GCC_LIKE } // namespace juce