VST3: fetch midi mappings all at once, use it for note/sound-off
[carla.git] / source / modules / juce_gui_basics / native / juce_win32_Windowing.cpp
blobf141d5885174ea1ee572b4734cda0579c1f5ab8e
1 /*
2 ==============================================================================
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
23 ==============================================================================
26 #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client
27 #include <juce_audio_plugin_client/AAX/juce_AAX_Modifier_Injector.h>
28 #endif
30 namespace juce
33 JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wcast-function-type")
35 #undef GetSystemMetrics // multimon overrides this for some reason and causes a mess..
37 // these are in the windows SDK, but need to be repeated here for GCC..
38 #ifndef GET_APPCOMMAND_LPARAM
39 #define GET_APPCOMMAND_LPARAM(lParam) ((short) (HIWORD (lParam) & ~FAPPCOMMAND_MASK))
41 #define FAPPCOMMAND_MASK 0xF000
42 #define APPCOMMAND_MEDIA_NEXTTRACK 11
43 #define APPCOMMAND_MEDIA_PREVIOUSTRACK 12
44 #define APPCOMMAND_MEDIA_STOP 13
45 #define APPCOMMAND_MEDIA_PLAY_PAUSE 14
46 #endif
48 #ifndef WM_APPCOMMAND
49 #define WM_APPCOMMAND 0x0319
50 #endif
52 void juce_repeatLastProcessPriority();
53 bool juce_isRunningInWine();
55 using CheckEventBlockedByModalComps = bool (*) (const MSG&);
56 extern CheckEventBlockedByModalComps isEventBlockedByModalComps;
58 static bool shouldDeactivateTitleBar = true;
60 void* getUser32Function (const char*);
62 #if JUCE_DEBUG
63 int numActiveScopedDpiAwarenessDisablers = 0;
64 static bool isInScopedDPIAwarenessDisabler() { return numActiveScopedDpiAwarenessDisablers > 0; }
65 extern HWND juce_messageWindowHandle;
66 #endif
68 struct ScopedDeviceContext
70 explicit ScopedDeviceContext (HWND h)
71 : hwnd (h), dc (GetDC (hwnd))
75 ~ScopedDeviceContext()
77 ReleaseDC (hwnd, dc);
80 HWND hwnd;
81 HDC dc;
83 JUCE_DECLARE_NON_COPYABLE (ScopedDeviceContext)
84 JUCE_DECLARE_NON_MOVEABLE (ScopedDeviceContext)
87 //==============================================================================
88 #ifndef WM_TOUCH
89 enum
91 WM_TOUCH = 0x0240,
92 TOUCHEVENTF_MOVE = 0x0001,
93 TOUCHEVENTF_DOWN = 0x0002,
94 TOUCHEVENTF_UP = 0x0004
97 typedef HANDLE HTOUCHINPUT;
98 typedef HANDLE HGESTUREINFO;
100 struct TOUCHINPUT
102 LONG x;
103 LONG y;
104 HANDLE hSource;
105 DWORD dwID;
106 DWORD dwFlags;
107 DWORD dwMask;
108 DWORD dwTime;
109 ULONG_PTR dwExtraInfo;
110 DWORD cxContact;
111 DWORD cyContact;
114 struct GESTUREINFO
116 UINT cbSize;
117 DWORD dwFlags;
118 DWORD dwID;
119 HWND hwndTarget;
120 POINTS ptsLocation;
121 DWORD dwInstanceID;
122 DWORD dwSequenceID;
123 ULONGLONG ullArguments;
124 UINT cbExtraArgs;
126 #endif
128 #ifndef WM_NCPOINTERUPDATE
129 enum
131 WM_NCPOINTERUPDATE = 0x241,
132 WM_NCPOINTERDOWN = 0x242,
133 WM_NCPOINTERUP = 0x243,
134 WM_POINTERUPDATE = 0x245,
135 WM_POINTERDOWN = 0x246,
136 WM_POINTERUP = 0x247,
137 WM_POINTERENTER = 0x249,
138 WM_POINTERLEAVE = 0x24A,
139 WM_POINTERACTIVATE = 0x24B,
140 WM_POINTERCAPTURECHANGED = 0x24C,
141 WM_TOUCHHITTESTING = 0x24D,
142 WM_POINTERWHEEL = 0x24E,
143 WM_POINTERHWHEEL = 0x24F,
144 WM_POINTERHITTEST = 0x250
147 enum
149 PT_TOUCH = 0x00000002,
150 PT_PEN = 0x00000003
153 enum POINTER_BUTTON_CHANGE_TYPE
155 POINTER_CHANGE_NONE,
156 POINTER_CHANGE_FIRSTBUTTON_DOWN,
157 POINTER_CHANGE_FIRSTBUTTON_UP,
158 POINTER_CHANGE_SECONDBUTTON_DOWN,
159 POINTER_CHANGE_SECONDBUTTON_UP,
160 POINTER_CHANGE_THIRDBUTTON_DOWN,
161 POINTER_CHANGE_THIRDBUTTON_UP,
162 POINTER_CHANGE_FOURTHBUTTON_DOWN,
163 POINTER_CHANGE_FOURTHBUTTON_UP,
164 POINTER_CHANGE_FIFTHBUTTON_DOWN,
165 POINTER_CHANGE_FIFTHBUTTON_UP
168 enum
170 PEN_MASK_NONE = 0x00000000,
171 PEN_MASK_PRESSURE = 0x00000001,
172 PEN_MASK_ROTATION = 0x00000002,
173 PEN_MASK_TILT_X = 0x00000004,
174 PEN_MASK_TILT_Y = 0x00000008
177 enum
179 TOUCH_MASK_NONE = 0x00000000,
180 TOUCH_MASK_CONTACTAREA = 0x00000001,
181 TOUCH_MASK_ORIENTATION = 0x00000002,
182 TOUCH_MASK_PRESSURE = 0x00000004
185 enum
187 POINTER_FLAG_NONE = 0x00000000,
188 POINTER_FLAG_NEW = 0x00000001,
189 POINTER_FLAG_INRANGE = 0x00000002,
190 POINTER_FLAG_INCONTACT = 0x00000004,
191 POINTER_FLAG_FIRSTBUTTON = 0x00000010,
192 POINTER_FLAG_SECONDBUTTON = 0x00000020,
193 POINTER_FLAG_THIRDBUTTON = 0x00000040,
194 POINTER_FLAG_FOURTHBUTTON = 0x00000080,
195 POINTER_FLAG_FIFTHBUTTON = 0x00000100,
196 POINTER_FLAG_PRIMARY = 0x00002000,
197 POINTER_FLAG_CONFIDENCE = 0x00004000,
198 POINTER_FLAG_CANCELED = 0x00008000,
199 POINTER_FLAG_DOWN = 0x00010000,
200 POINTER_FLAG_UPDATE = 0x00020000,
201 POINTER_FLAG_UP = 0x00040000,
202 POINTER_FLAG_WHEEL = 0x00080000,
203 POINTER_FLAG_HWHEEL = 0x00100000,
204 POINTER_FLAG_CAPTURECHANGED = 0x00200000,
205 POINTER_FLAG_HASTRANSFORM = 0x00400000
208 typedef DWORD POINTER_INPUT_TYPE;
209 typedef UINT32 POINTER_FLAGS;
210 typedef UINT32 PEN_FLAGS;
211 typedef UINT32 PEN_MASK;
212 typedef UINT32 TOUCH_FLAGS;
213 typedef UINT32 TOUCH_MASK;
215 struct POINTER_INFO
217 POINTER_INPUT_TYPE pointerType;
218 UINT32 pointerId;
219 UINT32 frameId;
220 POINTER_FLAGS pointerFlags;
221 HANDLE sourceDevice;
222 HWND hwndTarget;
223 POINT ptPixelLocation;
224 POINT ptHimetricLocation;
225 POINT ptPixelLocationRaw;
226 POINT ptHimetricLocationRaw;
227 DWORD dwTime;
228 UINT32 historyCount;
229 INT32 InputData;
230 DWORD dwKeyStates;
231 UINT64 PerformanceCount;
232 POINTER_BUTTON_CHANGE_TYPE ButtonChangeType;
235 struct POINTER_TOUCH_INFO
237 POINTER_INFO pointerInfo;
238 TOUCH_FLAGS touchFlags;
239 TOUCH_MASK touchMask;
240 RECT rcContact;
241 RECT rcContactRaw;
242 UINT32 orientation;
243 UINT32 pressure;
246 struct POINTER_PEN_INFO
248 POINTER_INFO pointerInfo;
249 PEN_FLAGS penFlags;
250 PEN_MASK penMask;
251 UINT32 pressure;
252 UINT32 rotation;
253 INT32 tiltX;
254 INT32 tiltY;
257 #define GET_POINTERID_WPARAM(wParam) (LOWORD(wParam))
258 #endif
260 #ifndef MONITOR_DPI_TYPE
261 enum Monitor_DPI_Type
263 MDT_Effective_DPI = 0,
264 MDT_Angular_DPI = 1,
265 MDT_Raw_DPI = 2,
266 MDT_Default = MDT_Effective_DPI
268 #endif
270 #ifndef DPI_AWARENESS
271 enum DPI_Awareness
273 DPI_Awareness_Invalid = -1,
274 DPI_Awareness_Unaware = 0,
275 DPI_Awareness_System_Aware = 1,
276 DPI_Awareness_Per_Monitor_Aware = 2
278 #endif
280 #ifndef USER_DEFAULT_SCREEN_DPI
281 #define USER_DEFAULT_SCREEN_DPI 96
282 #endif
284 #ifndef _DPI_AWARENESS_CONTEXTS_
285 typedef HANDLE DPI_AWARENESS_CONTEXT;
287 #define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT) - 1)
288 #define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT) - 2)
289 #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT) - 3)
290 #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT) - 4)
291 #endif
293 // Some versions of the Windows 10 SDK define _DPI_AWARENESS_CONTEXTS_ but not
294 // DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
295 #ifndef DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
296 #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT) - 4)
297 #endif
299 //==============================================================================
300 using RegisterTouchWindowFunc = BOOL (WINAPI*) (HWND, ULONG);
301 using GetTouchInputInfoFunc = BOOL (WINAPI*) (HTOUCHINPUT, UINT, TOUCHINPUT*, int);
302 using CloseTouchInputHandleFunc = BOOL (WINAPI*) (HTOUCHINPUT);
303 using GetGestureInfoFunc = BOOL (WINAPI*) (HGESTUREINFO, GESTUREINFO*);
305 static RegisterTouchWindowFunc registerTouchWindow = nullptr;
306 static GetTouchInputInfoFunc getTouchInputInfo = nullptr;
307 static CloseTouchInputHandleFunc closeTouchInputHandle = nullptr;
308 static GetGestureInfoFunc getGestureInfo = nullptr;
310 static bool hasCheckedForMultiTouch = false;
312 static bool canUseMultiTouch()
314 if (registerTouchWindow == nullptr && ! hasCheckedForMultiTouch)
316 hasCheckedForMultiTouch = true;
318 registerTouchWindow = (RegisterTouchWindowFunc) getUser32Function ("RegisterTouchWindow");
319 getTouchInputInfo = (GetTouchInputInfoFunc) getUser32Function ("GetTouchInputInfo");
320 closeTouchInputHandle = (CloseTouchInputHandleFunc) getUser32Function ("CloseTouchInputHandle");
321 getGestureInfo = (GetGestureInfoFunc) getUser32Function ("GetGestureInfo");
324 return registerTouchWindow != nullptr;
327 //==============================================================================
328 using GetPointerTypeFunc = BOOL (WINAPI*) (UINT32, POINTER_INPUT_TYPE*);
329 using GetPointerTouchInfoFunc = BOOL (WINAPI*) (UINT32, POINTER_TOUCH_INFO*);
330 using GetPointerPenInfoFunc = BOOL (WINAPI*) (UINT32, POINTER_PEN_INFO*);
332 static GetPointerTypeFunc getPointerTypeFunction = nullptr;
333 static GetPointerTouchInfoFunc getPointerTouchInfo = nullptr;
334 static GetPointerPenInfoFunc getPointerPenInfo = nullptr;
336 static bool canUsePointerAPI = false;
338 static void checkForPointerAPI()
340 getPointerTypeFunction = (GetPointerTypeFunc) getUser32Function ("GetPointerType");
341 getPointerTouchInfo = (GetPointerTouchInfoFunc) getUser32Function ("GetPointerTouchInfo");
342 getPointerPenInfo = (GetPointerPenInfoFunc) getUser32Function ("GetPointerPenInfo");
344 canUsePointerAPI = (getPointerTypeFunction != nullptr
345 && getPointerTouchInfo != nullptr
346 && getPointerPenInfo != nullptr);
349 //==============================================================================
350 using SetProcessDPIAwareFunc = BOOL (WINAPI*) ();
351 using SetProcessDPIAwarenessContextFunc = BOOL (WINAPI*) (DPI_AWARENESS_CONTEXT);
352 using SetProcessDPIAwarenessFunc = HRESULT (WINAPI*) (DPI_Awareness);
353 using SetThreadDPIAwarenessContextFunc = DPI_AWARENESS_CONTEXT (WINAPI*) (DPI_AWARENESS_CONTEXT);
354 using GetDPIForWindowFunc = UINT (WINAPI*) (HWND);
355 using GetDPIForMonitorFunc = HRESULT (WINAPI*) (HMONITOR, Monitor_DPI_Type, UINT*, UINT*);
356 using GetSystemMetricsForDpiFunc = int (WINAPI*) (int, UINT);
357 using GetProcessDPIAwarenessFunc = HRESULT (WINAPI*) (HANDLE, DPI_Awareness*);
358 using GetWindowDPIAwarenessContextFunc = DPI_AWARENESS_CONTEXT (WINAPI*) (HWND);
359 using GetThreadDPIAwarenessContextFunc = DPI_AWARENESS_CONTEXT (WINAPI*) ();
360 using GetAwarenessFromDpiAwarenessContextFunc = DPI_Awareness (WINAPI*) (DPI_AWARENESS_CONTEXT);
361 using EnableNonClientDPIScalingFunc = BOOL (WINAPI*) (HWND);
363 static SetProcessDPIAwareFunc setProcessDPIAware = nullptr;
364 static SetProcessDPIAwarenessContextFunc setProcessDPIAwarenessContext = nullptr;
365 static SetProcessDPIAwarenessFunc setProcessDPIAwareness = nullptr;
366 static SetThreadDPIAwarenessContextFunc setThreadDPIAwarenessContext = nullptr;
367 static GetDPIForMonitorFunc getDPIForMonitor = nullptr;
368 static GetDPIForWindowFunc getDPIForWindow = nullptr;
369 static GetProcessDPIAwarenessFunc getProcessDPIAwareness = nullptr;
370 static GetWindowDPIAwarenessContextFunc getWindowDPIAwarenessContext = nullptr;
371 static GetThreadDPIAwarenessContextFunc getThreadDPIAwarenessContext = nullptr;
372 static GetAwarenessFromDpiAwarenessContextFunc getAwarenessFromDPIAwarenessContext = nullptr;
373 static EnableNonClientDPIScalingFunc enableNonClientDPIScaling = nullptr;
375 static bool hasCheckedForDPIAwareness = false;
377 static void loadDPIAwarenessFunctions()
379 setProcessDPIAware = (SetProcessDPIAwareFunc) getUser32Function ("SetProcessDPIAware");
381 constexpr auto shcore = "SHCore.dll";
382 LoadLibraryA (shcore);
383 const auto shcoreModule = GetModuleHandleA (shcore);
385 if (shcoreModule == nullptr)
386 return;
388 getDPIForMonitor = (GetDPIForMonitorFunc) GetProcAddress (shcoreModule, "GetDpiForMonitor");
389 setProcessDPIAwareness = (SetProcessDPIAwarenessFunc) GetProcAddress (shcoreModule, "SetProcessDpiAwareness");
391 #if JUCE_WIN_PER_MONITOR_DPI_AWARE
392 getDPIForWindow = (GetDPIForWindowFunc) getUser32Function ("GetDpiForWindow");
393 getProcessDPIAwareness = (GetProcessDPIAwarenessFunc) GetProcAddress (shcoreModule, "GetProcessDpiAwareness");
394 getWindowDPIAwarenessContext = (GetWindowDPIAwarenessContextFunc) getUser32Function ("GetWindowDpiAwarenessContext");
395 setThreadDPIAwarenessContext = (SetThreadDPIAwarenessContextFunc) getUser32Function ("SetThreadDpiAwarenessContext");
396 getThreadDPIAwarenessContext = (GetThreadDPIAwarenessContextFunc) getUser32Function ("GetThreadDpiAwarenessContext");
397 getAwarenessFromDPIAwarenessContext = (GetAwarenessFromDpiAwarenessContextFunc) getUser32Function ("GetAwarenessFromDpiAwarenessContext");
398 setProcessDPIAwarenessContext = (SetProcessDPIAwarenessContextFunc) getUser32Function ("SetProcessDpiAwarenessContext");
399 enableNonClientDPIScaling = (EnableNonClientDPIScalingFunc) getUser32Function ("EnableNonClientDpiScaling");
400 #endif
403 static void setDPIAwareness()
405 if (hasCheckedForDPIAwareness)
406 return;
408 hasCheckedForDPIAwareness = true;
410 if (! JUCEApplicationBase::isStandaloneApp())
411 return;
413 loadDPIAwarenessFunctions();
415 if (setProcessDPIAwarenessContext != nullptr
416 && setProcessDPIAwarenessContext (DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2))
417 return;
419 if (setProcessDPIAwareness != nullptr && enableNonClientDPIScaling != nullptr
420 && SUCCEEDED (setProcessDPIAwareness (DPI_Awareness::DPI_Awareness_Per_Monitor_Aware)))
421 return;
423 if (setProcessDPIAwareness != nullptr && getDPIForMonitor != nullptr
424 && SUCCEEDED (setProcessDPIAwareness (DPI_Awareness::DPI_Awareness_System_Aware)))
425 return;
427 if (setProcessDPIAware != nullptr)
428 setProcessDPIAware();
431 static bool isPerMonitorDPIAwareProcess()
433 #if ! JUCE_WIN_PER_MONITOR_DPI_AWARE
434 return false;
435 #else
436 static bool dpiAware = []() -> bool
438 setDPIAwareness();
440 if (! JUCEApplication::isStandaloneApp())
441 return false;
443 if (getProcessDPIAwareness == nullptr)
444 return false;
446 DPI_Awareness context;
447 getProcessDPIAwareness (nullptr, &context);
449 return context == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware;
450 }();
452 return dpiAware;
453 #endif
456 static bool isPerMonitorDPIAwareWindow (HWND nativeWindow)
458 #if ! JUCE_WIN_PER_MONITOR_DPI_AWARE
459 ignoreUnused (nativeWindow);
460 return false;
461 #else
462 setDPIAwareness();
464 if (getWindowDPIAwarenessContext != nullptr
465 && getAwarenessFromDPIAwarenessContext != nullptr)
467 return (getAwarenessFromDPIAwarenessContext (getWindowDPIAwarenessContext (nativeWindow))
468 == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware);
471 return isPerMonitorDPIAwareProcess();
472 #endif
475 static bool isPerMonitorDPIAwareThread (GetThreadDPIAwarenessContextFunc getThreadDPIAwarenessContextIn = getThreadDPIAwarenessContext,
476 GetAwarenessFromDpiAwarenessContextFunc getAwarenessFromDPIAwarenessContextIn = getAwarenessFromDPIAwarenessContext)
478 #if ! JUCE_WIN_PER_MONITOR_DPI_AWARE
479 return false;
480 #else
481 setDPIAwareness();
483 if (getThreadDPIAwarenessContextIn != nullptr
484 && getAwarenessFromDPIAwarenessContextIn != nullptr)
486 return (getAwarenessFromDPIAwarenessContextIn (getThreadDPIAwarenessContextIn())
487 == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware);
490 return isPerMonitorDPIAwareProcess();
491 #endif
494 static double getGlobalDPI()
496 setDPIAwareness();
498 ScopedDeviceContext deviceContext { nullptr };
499 return (GetDeviceCaps (deviceContext.dc, LOGPIXELSX) + GetDeviceCaps (deviceContext.dc, LOGPIXELSY)) / 2.0;
502 //==============================================================================
503 class ScopedThreadDPIAwarenessSetter::NativeImpl
505 public:
506 explicit NativeImpl (HWND nativeWindow)
508 ignoreUnused (nativeWindow);
510 #if JUCE_WIN_PER_MONITOR_DPI_AWARE
511 if (auto* functionSingleton = FunctionSingleton::getInstance())
513 if (! functionSingleton->isLoaded())
514 return;
516 auto dpiAwareWindow = (functionSingleton->getAwarenessFromContext (functionSingleton->getWindowAwareness (nativeWindow))
517 == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware);
519 auto dpiAwareThread = (functionSingleton->getAwarenessFromContext (functionSingleton->getThreadAwareness())
520 == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware);
522 if (dpiAwareWindow && ! dpiAwareThread)
523 oldContext = functionSingleton->setThreadAwareness (DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
524 else if (! dpiAwareWindow && dpiAwareThread)
525 oldContext = functionSingleton->setThreadAwareness (DPI_AWARENESS_CONTEXT_UNAWARE);
527 #endif
530 ~NativeImpl()
532 if (oldContext != nullptr)
533 if (auto* functionSingleton = FunctionSingleton::getInstance())
534 functionSingleton->setThreadAwareness (oldContext);
537 private:
538 struct FunctionSingleton : public DeletedAtShutdown
540 FunctionSingleton() = default;
541 ~FunctionSingleton() override { clearSingletonInstance(); }
543 SetThreadDPIAwarenessContextFunc setThreadAwareness = (SetThreadDPIAwarenessContextFunc) getUser32Function ("SetThreadDpiAwarenessContext");
544 GetWindowDPIAwarenessContextFunc getWindowAwareness = (GetWindowDPIAwarenessContextFunc) getUser32Function ("GetWindowDpiAwarenessContext");
545 GetThreadDPIAwarenessContextFunc getThreadAwareness = (GetThreadDPIAwarenessContextFunc) getUser32Function ("GetThreadDpiAwarenessContext");
546 GetAwarenessFromDpiAwarenessContextFunc getAwarenessFromContext = (GetAwarenessFromDpiAwarenessContextFunc) getUser32Function ("GetAwarenessFromDpiAwarenessContext");
548 bool isLoaded() const noexcept
550 return setThreadAwareness != nullptr
551 && getWindowAwareness != nullptr
552 && getThreadAwareness != nullptr
553 && getAwarenessFromContext != nullptr;
556 JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (FunctionSingleton)
558 JUCE_DECLARE_NON_COPYABLE (FunctionSingleton)
559 JUCE_DECLARE_NON_MOVEABLE (FunctionSingleton)
562 DPI_AWARENESS_CONTEXT oldContext = nullptr;
564 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeImpl)
565 JUCE_DECLARE_NON_MOVEABLE (NativeImpl)
569 JUCE_IMPLEMENT_SINGLETON (ScopedThreadDPIAwarenessSetter::NativeImpl::FunctionSingleton)
571 ScopedThreadDPIAwarenessSetter::ScopedThreadDPIAwarenessSetter (void* nativeWindow)
573 pimpl = std::make_unique<NativeImpl> ((HWND) nativeWindow);
576 ScopedThreadDPIAwarenessSetter::~ScopedThreadDPIAwarenessSetter() = default;
578 ScopedDPIAwarenessDisabler::ScopedDPIAwarenessDisabler()
580 static auto localGetThreadDpiAwarenessContext = (GetThreadDPIAwarenessContextFunc) getUser32Function ("GetThreadDpiAwarenessContext");
581 static auto localGetAwarenessFromDpiAwarenessContextFunc = (GetAwarenessFromDpiAwarenessContextFunc) getUser32Function ("GetAwarenessFromDpiAwarenessContext");
583 if (! isPerMonitorDPIAwareThread (localGetThreadDpiAwarenessContext, localGetAwarenessFromDpiAwarenessContextFunc))
584 return;
586 static auto localSetThreadDPIAwarenessContext = (SetThreadDPIAwarenessContextFunc) getUser32Function ("SetThreadDpiAwarenessContext");
588 if (localSetThreadDPIAwarenessContext != nullptr)
590 previousContext = localSetThreadDPIAwarenessContext (DPI_AWARENESS_CONTEXT_UNAWARE);
592 #if JUCE_DEBUG
593 ++numActiveScopedDpiAwarenessDisablers;
594 #endif
598 ScopedDPIAwarenessDisabler::~ScopedDPIAwarenessDisabler()
600 if (previousContext != nullptr)
602 static auto localSetThreadDPIAwarenessContext = (SetThreadDPIAwarenessContextFunc) getUser32Function ("SetThreadDpiAwarenessContext");
604 if (localSetThreadDPIAwarenessContext != nullptr)
605 localSetThreadDPIAwarenessContext ((DPI_AWARENESS_CONTEXT) previousContext);
607 #if JUCE_DEBUG
608 --numActiveScopedDpiAwarenessDisablers;
609 #endif
613 //==============================================================================
614 using SettingChangeCallbackFunc = void (*)(void);
615 extern SettingChangeCallbackFunc settingChangeCallback;
617 //==============================================================================
618 static Rectangle<int> rectangleFromRECT (RECT r) noexcept { return { r.left, r.top, r.right - r.left, r.bottom - r.top }; }
619 static RECT RECTFromRectangle (Rectangle<int> r) noexcept { return { r.getX(), r.getY(), r.getRight(), r.getBottom() }; }
621 static Point<int> pointFromPOINT (POINT p) noexcept { return { p.x, p.y }; }
622 static POINT POINTFromPoint (Point<int> p) noexcept { return { p.x, p.y }; }
624 //==============================================================================
625 static const Displays::Display* getCurrentDisplayFromScaleFactor (HWND hwnd);
627 template <typename ValueType>
628 static Rectangle<ValueType> convertPhysicalScreenRectangleToLogical (Rectangle<ValueType> r, HWND h) noexcept
630 if (isPerMonitorDPIAwareWindow (h))
631 return Desktop::getInstance().getDisplays().physicalToLogical (r, getCurrentDisplayFromScaleFactor (h));
633 return r;
636 template <typename ValueType>
637 static Rectangle<ValueType> convertLogicalScreenRectangleToPhysical (Rectangle<ValueType> r, HWND h) noexcept
639 if (isPerMonitorDPIAwareWindow (h))
640 return Desktop::getInstance().getDisplays().logicalToPhysical (r, getCurrentDisplayFromScaleFactor (h));
642 return r;
645 static Point<int> convertPhysicalScreenPointToLogical (Point<int> p, HWND h) noexcept
647 if (isPerMonitorDPIAwareWindow (h))
648 return Desktop::getInstance().getDisplays().physicalToLogical (p, getCurrentDisplayFromScaleFactor (h));
650 return p;
653 static Point<int> convertLogicalScreenPointToPhysical (Point<int> p, HWND h) noexcept
655 if (isPerMonitorDPIAwareWindow (h))
656 return Desktop::getInstance().getDisplays().logicalToPhysical (p, getCurrentDisplayFromScaleFactor (h));
658 return p;
661 JUCE_API double getScaleFactorForWindow (HWND h);
662 JUCE_API double getScaleFactorForWindow (HWND h)
664 // NB. Using a local function here because we need to call this method from the plug-in wrappers
665 // which don't load the DPI-awareness functions on startup
666 static GetDPIForWindowFunc localGetDPIForWindow = nullptr;
668 static bool hasChecked = false;
670 if (! hasChecked)
672 hasChecked = true;
674 if (localGetDPIForWindow == nullptr)
675 localGetDPIForWindow = (GetDPIForWindowFunc) getUser32Function ("GetDpiForWindow");
678 if (localGetDPIForWindow != nullptr)
679 return (double) localGetDPIForWindow (h) / USER_DEFAULT_SCREEN_DPI;
681 return 1.0;
684 //==============================================================================
685 static void setWindowPos (HWND hwnd, Rectangle<int> bounds, UINT flags, bool adjustTopLeft = false)
687 ScopedThreadDPIAwarenessSetter setter { hwnd };
689 if (isPerMonitorDPIAwareWindow (hwnd))
691 if (adjustTopLeft)
692 bounds = convertLogicalScreenRectangleToPhysical (bounds, hwnd)
693 .withPosition (Desktop::getInstance().getDisplays().logicalToPhysical (bounds.getTopLeft()));
694 else
695 bounds = convertLogicalScreenRectangleToPhysical (bounds, hwnd);
698 SetWindowPos (hwnd, nullptr, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), flags);
701 static RECT getWindowScreenRect (HWND hwnd)
703 ScopedThreadDPIAwarenessSetter setter { hwnd };
705 RECT rect;
706 GetWindowRect (hwnd, &rect);
707 return rect;
710 static RECT getWindowClientRect (HWND hwnd)
712 auto rect = getWindowScreenRect (hwnd);
714 if (auto parentH = GetParent (hwnd))
716 ScopedThreadDPIAwarenessSetter setter { hwnd };
717 MapWindowPoints (HWND_DESKTOP, parentH, (LPPOINT) &rect, 2);
720 return rect;
723 static void setWindowZOrder (HWND hwnd, HWND insertAfter)
725 SetWindowPos (hwnd, insertAfter, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING);
728 //==============================================================================
729 #if ! JUCE_MINGW
730 extern RTL_OSVERSIONINFOW getWindowsVersionInfo();
731 #endif
733 double Desktop::getDefaultMasterScale()
735 if (! JUCEApplicationBase::isStandaloneApp() || isPerMonitorDPIAwareProcess())
736 return 1.0;
738 return getGlobalDPI() / USER_DEFAULT_SCREEN_DPI;
741 bool Desktop::canUseSemiTransparentWindows() noexcept
743 return true;
746 class Desktop::NativeDarkModeChangeDetectorImpl
748 public:
749 NativeDarkModeChangeDetectorImpl()
751 #if ! JUCE_MINGW
752 const auto winVer = getWindowsVersionInfo();
754 if (winVer.dwMajorVersion >= 10 && winVer.dwBuildNumber >= 17763)
756 const auto uxtheme = "uxtheme.dll";
757 LoadLibraryA (uxtheme);
758 const auto uxthemeModule = GetModuleHandleA (uxtheme);
760 if (uxthemeModule != nullptr)
762 shouldAppsUseDarkMode = (ShouldAppsUseDarkModeFunc) GetProcAddress (uxthemeModule, MAKEINTRESOURCEA (132));
764 if (shouldAppsUseDarkMode != nullptr)
765 darkModeEnabled = shouldAppsUseDarkMode() && ! isHighContrast();
768 #endif
771 ~NativeDarkModeChangeDetectorImpl()
773 UnhookWindowsHookEx (hook);
776 bool isDarkModeEnabled() const noexcept { return darkModeEnabled; }
778 private:
779 static bool isHighContrast()
781 HIGHCONTRASTW highContrast {};
783 if (SystemParametersInfoW (SPI_GETHIGHCONTRAST, sizeof (highContrast), &highContrast, false))
784 return highContrast.dwFlags & HCF_HIGHCONTRASTON;
786 return false;
789 static LRESULT CALLBACK callWndProc (int nCode, WPARAM wParam, LPARAM lParam)
791 auto* params = reinterpret_cast<CWPSTRUCT*> (lParam);
793 if (nCode >= 0
794 && params != nullptr
795 && params->message == WM_SETTINGCHANGE
796 && params->lParam != 0
797 && CompareStringOrdinal (reinterpret_cast<LPWCH> (params->lParam), -1, L"ImmersiveColorSet", -1, true) == CSTR_EQUAL)
799 Desktop::getInstance().nativeDarkModeChangeDetectorImpl->colourSetChanged();
802 return CallNextHookEx ({}, nCode, wParam, lParam);
805 void colourSetChanged()
807 if (shouldAppsUseDarkMode != nullptr)
809 const auto wasDarkModeEnabled = std::exchange (darkModeEnabled, shouldAppsUseDarkMode() && ! isHighContrast());
811 if (darkModeEnabled != wasDarkModeEnabled)
812 Desktop::getInstance().darkModeChanged();
816 using ShouldAppsUseDarkModeFunc = bool (WINAPI*)();
817 ShouldAppsUseDarkModeFunc shouldAppsUseDarkMode = nullptr;
819 bool darkModeEnabled = false;
820 HHOOK hook { SetWindowsHookEx (WH_CALLWNDPROC,
821 callWndProc,
822 (HINSTANCE) juce::Process::getCurrentModuleInstanceHandle(),
823 GetCurrentThreadId()) };
825 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeDarkModeChangeDetectorImpl)
828 std::unique_ptr<Desktop::NativeDarkModeChangeDetectorImpl> Desktop::createNativeDarkModeChangeDetectorImpl()
830 return std::make_unique<NativeDarkModeChangeDetectorImpl>();
833 bool Desktop::isDarkModeActive() const
835 return nativeDarkModeChangeDetectorImpl->isDarkModeEnabled();
838 Desktop::DisplayOrientation Desktop::getCurrentOrientation() const
840 return upright;
843 int64 getMouseEventTime();
844 int64 getMouseEventTime()
846 static int64 eventTimeOffset = 0;
847 static LONG lastMessageTime = 0;
848 const LONG thisMessageTime = GetMessageTime();
850 if (thisMessageTime < lastMessageTime || lastMessageTime == 0)
852 lastMessageTime = thisMessageTime;
853 eventTimeOffset = Time::currentTimeMillis() - thisMessageTime;
856 return eventTimeOffset + thisMessageTime;
859 //==============================================================================
860 const int extendedKeyModifier = 0x10000;
862 const int KeyPress::spaceKey = VK_SPACE;
863 const int KeyPress::returnKey = VK_RETURN;
864 const int KeyPress::escapeKey = VK_ESCAPE;
865 const int KeyPress::backspaceKey = VK_BACK;
866 const int KeyPress::deleteKey = VK_DELETE | extendedKeyModifier;
867 const int KeyPress::insertKey = VK_INSERT | extendedKeyModifier;
868 const int KeyPress::tabKey = VK_TAB;
869 const int KeyPress::leftKey = VK_LEFT | extendedKeyModifier;
870 const int KeyPress::rightKey = VK_RIGHT | extendedKeyModifier;
871 const int KeyPress::upKey = VK_UP | extendedKeyModifier;
872 const int KeyPress::downKey = VK_DOWN | extendedKeyModifier;
873 const int KeyPress::homeKey = VK_HOME | extendedKeyModifier;
874 const int KeyPress::endKey = VK_END | extendedKeyModifier;
875 const int KeyPress::pageUpKey = VK_PRIOR | extendedKeyModifier;
876 const int KeyPress::pageDownKey = VK_NEXT | extendedKeyModifier;
877 const int KeyPress::F1Key = VK_F1 | extendedKeyModifier;
878 const int KeyPress::F2Key = VK_F2 | extendedKeyModifier;
879 const int KeyPress::F3Key = VK_F3 | extendedKeyModifier;
880 const int KeyPress::F4Key = VK_F4 | extendedKeyModifier;
881 const int KeyPress::F5Key = VK_F5 | extendedKeyModifier;
882 const int KeyPress::F6Key = VK_F6 | extendedKeyModifier;
883 const int KeyPress::F7Key = VK_F7 | extendedKeyModifier;
884 const int KeyPress::F8Key = VK_F8 | extendedKeyModifier;
885 const int KeyPress::F9Key = VK_F9 | extendedKeyModifier;
886 const int KeyPress::F10Key = VK_F10 | extendedKeyModifier;
887 const int KeyPress::F11Key = VK_F11 | extendedKeyModifier;
888 const int KeyPress::F12Key = VK_F12 | extendedKeyModifier;
889 const int KeyPress::F13Key = VK_F13 | extendedKeyModifier;
890 const int KeyPress::F14Key = VK_F14 | extendedKeyModifier;
891 const int KeyPress::F15Key = VK_F15 | extendedKeyModifier;
892 const int KeyPress::F16Key = VK_F16 | extendedKeyModifier;
893 const int KeyPress::F17Key = VK_F17 | extendedKeyModifier;
894 const int KeyPress::F18Key = VK_F18 | extendedKeyModifier;
895 const int KeyPress::F19Key = VK_F19 | extendedKeyModifier;
896 const int KeyPress::F20Key = VK_F20 | extendedKeyModifier;
897 const int KeyPress::F21Key = VK_F21 | extendedKeyModifier;
898 const int KeyPress::F22Key = VK_F22 | extendedKeyModifier;
899 const int KeyPress::F23Key = VK_F23 | extendedKeyModifier;
900 const int KeyPress::F24Key = VK_F24 | extendedKeyModifier;
901 const int KeyPress::F25Key = 0x31000; // Windows doesn't support F-keys 25 or higher
902 const int KeyPress::F26Key = 0x31001;
903 const int KeyPress::F27Key = 0x31002;
904 const int KeyPress::F28Key = 0x31003;
905 const int KeyPress::F29Key = 0x31004;
906 const int KeyPress::F30Key = 0x31005;
907 const int KeyPress::F31Key = 0x31006;
908 const int KeyPress::F32Key = 0x31007;
909 const int KeyPress::F33Key = 0x31008;
910 const int KeyPress::F34Key = 0x31009;
911 const int KeyPress::F35Key = 0x3100a;
913 const int KeyPress::numberPad0 = VK_NUMPAD0 | extendedKeyModifier;
914 const int KeyPress::numberPad1 = VK_NUMPAD1 | extendedKeyModifier;
915 const int KeyPress::numberPad2 = VK_NUMPAD2 | extendedKeyModifier;
916 const int KeyPress::numberPad3 = VK_NUMPAD3 | extendedKeyModifier;
917 const int KeyPress::numberPad4 = VK_NUMPAD4 | extendedKeyModifier;
918 const int KeyPress::numberPad5 = VK_NUMPAD5 | extendedKeyModifier;
919 const int KeyPress::numberPad6 = VK_NUMPAD6 | extendedKeyModifier;
920 const int KeyPress::numberPad7 = VK_NUMPAD7 | extendedKeyModifier;
921 const int KeyPress::numberPad8 = VK_NUMPAD8 | extendedKeyModifier;
922 const int KeyPress::numberPad9 = VK_NUMPAD9 | extendedKeyModifier;
923 const int KeyPress::numberPadAdd = VK_ADD | extendedKeyModifier;
924 const int KeyPress::numberPadSubtract = VK_SUBTRACT | extendedKeyModifier;
925 const int KeyPress::numberPadMultiply = VK_MULTIPLY | extendedKeyModifier;
926 const int KeyPress::numberPadDivide = VK_DIVIDE | extendedKeyModifier;
927 const int KeyPress::numberPadSeparator = VK_SEPARATOR | extendedKeyModifier;
928 const int KeyPress::numberPadDecimalPoint = VK_DECIMAL | extendedKeyModifier;
929 const int KeyPress::numberPadEquals = 0x92 /*VK_OEM_NEC_EQUAL*/ | extendedKeyModifier;
930 const int KeyPress::numberPadDelete = VK_DELETE | extendedKeyModifier;
931 const int KeyPress::playKey = 0x30000;
932 const int KeyPress::stopKey = 0x30001;
933 const int KeyPress::fastForwardKey = 0x30002;
934 const int KeyPress::rewindKey = 0x30003;
937 //==============================================================================
938 class WindowsBitmapImage : public ImagePixelData
940 public:
941 WindowsBitmapImage (const Image::PixelFormat format,
942 const int w, const int h, const bool clearImage)
943 : ImagePixelData (format, w, h)
945 jassert (format == Image::RGB || format == Image::ARGB);
947 static bool alwaysUse32Bits = isGraphicsCard32Bit(); // NB: for 32-bit cards, it's faster to use a 32-bit image.
949 pixelStride = (alwaysUse32Bits || format == Image::ARGB) ? 4 : 3;
950 lineStride = -((w * pixelStride + 3) & ~3);
952 zerostruct (bitmapInfo);
953 bitmapInfo.bV4Size = sizeof (BITMAPV4HEADER);
954 bitmapInfo.bV4Width = w;
955 bitmapInfo.bV4Height = h;
956 bitmapInfo.bV4Planes = 1;
957 bitmapInfo.bV4CSType = 1;
958 bitmapInfo.bV4BitCount = (unsigned short) (pixelStride * 8);
960 if (format == Image::ARGB)
962 bitmapInfo.bV4AlphaMask = 0xff000000;
963 bitmapInfo.bV4RedMask = 0xff0000;
964 bitmapInfo.bV4GreenMask = 0xff00;
965 bitmapInfo.bV4BlueMask = 0xff;
966 bitmapInfo.bV4V4Compression = BI_BITFIELDS;
968 else
970 bitmapInfo.bV4V4Compression = BI_RGB;
974 ScopedDeviceContext deviceContext { nullptr };
975 hdc = CreateCompatibleDC (deviceContext.dc);
978 SetMapMode (hdc, MM_TEXT);
980 hBitmap = CreateDIBSection (hdc, (BITMAPINFO*) &(bitmapInfo), DIB_RGB_COLORS,
981 (void**) &bitmapData, nullptr, 0);
983 if (hBitmap != nullptr)
984 previousBitmap = SelectObject (hdc, hBitmap);
986 if (format == Image::ARGB && clearImage)
987 zeromem (bitmapData, (size_t) std::abs (h * lineStride));
989 imageData = bitmapData - (lineStride * (h - 1));
992 ~WindowsBitmapImage() override
994 SelectObject (hdc, previousBitmap); // Selecting the previous bitmap before deleting the DC avoids a warning in BoundsChecker
995 DeleteDC (hdc);
996 DeleteObject (hBitmap);
999 std::unique_ptr<ImageType> createType() const override { return std::make_unique<NativeImageType>(); }
1001 std::unique_ptr<LowLevelGraphicsContext> createLowLevelContext() override
1003 sendDataChangeMessage();
1004 return std::make_unique<LowLevelGraphicsSoftwareRenderer> (Image (this));
1007 void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override
1009 const auto offset = (size_t) (x * pixelStride + y * lineStride);
1010 bitmap.data = imageData + offset;
1011 bitmap.size = (size_t) (lineStride * height) - offset;
1012 bitmap.pixelFormat = pixelFormat;
1013 bitmap.lineStride = lineStride;
1014 bitmap.pixelStride = pixelStride;
1016 if (mode != Image::BitmapData::readOnly)
1017 sendDataChangeMessage();
1020 ImagePixelData::Ptr clone() override
1022 auto im = new WindowsBitmapImage (pixelFormat, width, height, false);
1024 for (int i = 0; i < height; ++i)
1025 memcpy (im->imageData + i * lineStride, imageData + i * lineStride, (size_t) lineStride);
1027 return im;
1030 void blitToWindow (HWND hwnd, HDC dc, bool transparent, int x, int y, uint8 updateLayeredWindowAlpha) noexcept
1032 SetMapMode (dc, MM_TEXT);
1034 if (transparent)
1036 auto windowBounds = getWindowScreenRect (hwnd);
1038 POINT p = { -x, -y };
1039 POINT pos = { windowBounds.left, windowBounds.top };
1040 SIZE size = { windowBounds.right - windowBounds.left,
1041 windowBounds.bottom - windowBounds.top };
1043 BLENDFUNCTION bf;
1044 bf.AlphaFormat = 1 /*AC_SRC_ALPHA*/;
1045 bf.BlendFlags = 0;
1046 bf.BlendOp = AC_SRC_OVER;
1047 bf.SourceConstantAlpha = updateLayeredWindowAlpha;
1049 UpdateLayeredWindow (hwnd, nullptr, &pos, &size, hdc, &p, 0, &bf, 2 /*ULW_ALPHA*/);
1051 else
1053 StretchDIBits (dc,
1054 x, y, width, height,
1055 0, 0, width, height,
1056 bitmapData, (const BITMAPINFO*) &bitmapInfo,
1057 DIB_RGB_COLORS, SRCCOPY);
1061 HBITMAP hBitmap;
1062 HGDIOBJ previousBitmap;
1063 BITMAPV4HEADER bitmapInfo;
1064 HDC hdc;
1065 uint8* bitmapData;
1066 int pixelStride, lineStride;
1067 uint8* imageData;
1069 private:
1070 static bool isGraphicsCard32Bit()
1072 ScopedDeviceContext deviceContext { nullptr };
1073 return GetDeviceCaps (deviceContext.dc, BITSPIXEL) > 24;
1076 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsBitmapImage)
1079 //==============================================================================
1080 Image createSnapshotOfNativeWindow (void* nativeWindowHandle)
1082 auto hwnd = (HWND) nativeWindowHandle;
1084 auto r = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (hwnd)), hwnd);
1085 const auto w = r.getWidth();
1086 const auto h = r.getHeight();
1088 auto nativeBitmap = new WindowsBitmapImage (Image::RGB, w, h, true);
1089 Image bitmap (nativeBitmap);
1091 ScopedDeviceContext deviceContext { hwnd };
1093 if (isPerMonitorDPIAwareProcess())
1095 auto scale = getScaleFactorForWindow (hwnd);
1096 auto prevStretchMode = SetStretchBltMode (nativeBitmap->hdc, HALFTONE);
1097 SetBrushOrgEx (nativeBitmap->hdc, 0, 0, nullptr);
1099 StretchBlt (nativeBitmap->hdc, 0, 0, w, h,
1100 deviceContext.dc, 0, 0, roundToInt (w * scale), roundToInt (h * scale),
1101 SRCCOPY);
1103 SetStretchBltMode (nativeBitmap->hdc, prevStretchMode);
1105 else
1107 BitBlt (nativeBitmap->hdc, 0, 0, w, h, deviceContext.dc, 0, 0, SRCCOPY);
1110 return SoftwareImageType().convert (bitmap);
1113 //==============================================================================
1114 namespace IconConverters
1116 static Image createImageFromHICON (HICON icon)
1118 if (icon == nullptr)
1119 return {};
1121 struct ScopedICONINFO : public ICONINFO
1123 ScopedICONINFO()
1125 hbmColor = nullptr;
1126 hbmMask = nullptr;
1129 ~ScopedICONINFO()
1131 if (hbmColor != nullptr)
1132 ::DeleteObject (hbmColor);
1134 if (hbmMask != nullptr)
1135 ::DeleteObject (hbmMask);
1139 ScopedICONINFO info;
1141 if (! ::GetIconInfo (icon, &info))
1142 return {};
1144 BITMAP bm;
1146 if (! (::GetObject (info.hbmColor, sizeof (BITMAP), &bm)
1147 && bm.bmWidth > 0 && bm.bmHeight > 0))
1148 return {};
1150 ScopedDeviceContext deviceContext { nullptr };
1152 if (auto* dc = ::CreateCompatibleDC (deviceContext.dc))
1154 BITMAPV5HEADER header = {};
1155 header.bV5Size = sizeof (BITMAPV5HEADER);
1156 header.bV5Width = bm.bmWidth;
1157 header.bV5Height = -bm.bmHeight;
1158 header.bV5Planes = 1;
1159 header.bV5Compression = BI_RGB;
1160 header.bV5BitCount = 32;
1161 header.bV5RedMask = 0x00FF0000;
1162 header.bV5GreenMask = 0x0000FF00;
1163 header.bV5BlueMask = 0x000000FF;
1164 header.bV5AlphaMask = 0xFF000000;
1165 header.bV5CSType = 0x57696E20; // 'Win '
1166 header.bV5Intent = LCS_GM_IMAGES;
1168 uint32* bitmapImageData = nullptr;
1170 if (auto* dib = ::CreateDIBSection (deviceContext.dc, (BITMAPINFO*) &header, DIB_RGB_COLORS,
1171 (void**) &bitmapImageData, nullptr, 0))
1173 auto oldObject = ::SelectObject (dc, dib);
1175 auto numPixels = bm.bmWidth * bm.bmHeight;
1176 auto numColourComponents = (size_t) numPixels * 4;
1178 // Windows icon data comes as two layers, an XOR mask which contains the bulk
1179 // of the image data and an AND mask which provides the transparency. Annoyingly
1180 // the XOR mask can also contain an alpha channel, in which case the transparency
1181 // mask should not be applied, but there's no way to find out a priori if the XOR
1182 // mask contains an alpha channel.
1184 HeapBlock<bool> opacityMask (numPixels);
1185 memset (bitmapImageData, 0, numColourComponents);
1186 ::DrawIconEx (dc, 0, 0, icon, bm.bmWidth, bm.bmHeight, 0, nullptr, DI_MASK);
1188 for (int i = 0; i < numPixels; ++i)
1189 opacityMask[i] = (bitmapImageData[i] == 0);
1191 Image result = Image (Image::ARGB, bm.bmWidth, bm.bmHeight, true);
1192 Image::BitmapData imageData (result, Image::BitmapData::readWrite);
1194 memset (bitmapImageData, 0, numColourComponents);
1195 ::DrawIconEx (dc, 0, 0, icon, bm.bmWidth, bm.bmHeight, 0, nullptr, DI_NORMAL);
1196 memcpy (imageData.data, bitmapImageData, numColourComponents);
1198 auto imageHasAlphaChannel = [&imageData, numPixels]()
1200 for (int i = 0; i < numPixels; ++i)
1201 if (imageData.data[i * 4] != 0)
1202 return true;
1204 return false;
1207 if (! imageHasAlphaChannel())
1208 for (int i = 0; i < numPixels; ++i)
1209 imageData.data[i * 4] = opacityMask[i] ? 0xff : 0x00;
1211 ::SelectObject (dc, oldObject);
1212 ::DeleteObject (dib);
1213 ::DeleteDC (dc);
1215 return result;
1218 ::DeleteDC (dc);
1221 return {};
1224 HICON createHICONFromImage (const Image& image, const BOOL isIcon, int hotspotX, int hotspotY);
1225 HICON createHICONFromImage (const Image& image, const BOOL isIcon, int hotspotX, int hotspotY)
1227 auto nativeBitmap = new WindowsBitmapImage (Image::ARGB, image.getWidth(), image.getHeight(), true);
1228 Image bitmap (nativeBitmap);
1231 Graphics g (bitmap);
1232 g.drawImageAt (image, 0, 0);
1235 auto mask = CreateBitmap (image.getWidth(), image.getHeight(), 1, 1, nullptr);
1237 ICONINFO info;
1238 info.fIcon = isIcon;
1239 info.xHotspot = (DWORD) hotspotX;
1240 info.yHotspot = (DWORD) hotspotY;
1241 info.hbmMask = mask;
1242 info.hbmColor = nativeBitmap->hBitmap;
1244 auto hi = CreateIconIndirect (&info);
1245 DeleteObject (mask);
1246 return hi;
1248 } // namespace IconConverters
1250 //==============================================================================
1251 JUCE_IUNKNOWNCLASS (ITipInvocation, "37c994e7-432b-4834-a2f7-dce1f13b834b")
1253 static CLSID getCLSID() noexcept { return { 0x4ce576fa, 0x83dc, 0x4f88, { 0x95, 0x1c, 0x9d, 0x07, 0x82, 0xb4, 0xe3, 0x76 } }; }
1255 JUCE_COMCALL Toggle (HWND) = 0;
1258 } // namespace juce
1260 #ifdef __CRT_UUID_DECL
1261 __CRT_UUID_DECL (juce::ITipInvocation, 0x37c994e7, 0x432b, 0x4834, 0xa2, 0xf7, 0xdc, 0xe1, 0xf1, 0x3b, 0x83, 0x4b)
1262 #endif
1264 namespace juce
1267 //==============================================================================
1268 struct HSTRING_PRIVATE;
1269 typedef HSTRING_PRIVATE* HSTRING;
1271 struct IInspectable : public IUnknown
1273 JUCE_COMCALL GetIids (ULONG* ,IID**) = 0;
1274 JUCE_COMCALL GetRuntimeClassName (HSTRING*) = 0;
1275 JUCE_COMCALL GetTrustLevel (void*) = 0;
1278 JUCE_COMCLASS (IUIViewSettingsInterop, "3694dbf9-8f68-44be-8ff5-195c98ede8a6") : public IInspectable
1280 JUCE_COMCALL GetForWindow (HWND, REFIID, void**) = 0;
1283 JUCE_COMCLASS (IUIViewSettings, "c63657f6-8850-470d-88f8-455e16ea2c26") : public IInspectable
1285 enum UserInteractionMode
1287 Mouse = 0,
1288 Touch = 1
1291 JUCE_COMCALL GetUserInteractionMode (UserInteractionMode*) = 0;
1294 } // namespace juce
1296 #ifdef __CRT_UUID_DECL
1297 __CRT_UUID_DECL (juce::IUIViewSettingsInterop, 0x3694dbf9, 0x8f68, 0x44be, 0x8f, 0xf5, 0x19, 0x5c, 0x98, 0xed, 0xe8, 0xa6)
1298 __CRT_UUID_DECL (juce::IUIViewSettings, 0xc63657f6, 0x8850, 0x470d, 0x88, 0xf8, 0x45, 0x5e, 0x16, 0xea, 0x2c, 0x26)
1299 #endif
1301 namespace juce
1303 struct UWPUIViewSettings
1305 UWPUIViewSettings()
1307 ComBaseModule dll (L"api-ms-win-core-winrt-l1-1-0");
1309 if (dll.h != nullptr)
1311 roInitialize = (RoInitializeFuncPtr) ::GetProcAddress (dll.h, "RoInitialize");
1312 roGetActivationFactory = (RoGetActivationFactoryFuncPtr) ::GetProcAddress (dll.h, "RoGetActivationFactory");
1313 createHString = (WindowsCreateStringFuncPtr) ::GetProcAddress (dll.h, "WindowsCreateString");
1314 deleteHString = (WindowsDeleteStringFuncPtr) ::GetProcAddress (dll.h, "WindowsDeleteString");
1316 if (roInitialize == nullptr || roGetActivationFactory == nullptr
1317 || createHString == nullptr || deleteHString == nullptr)
1318 return;
1320 auto status = roInitialize (1);
1322 if (status != S_OK && status != S_FALSE && (unsigned) status != 0x80010106L)
1323 return;
1325 LPCWSTR uwpClassName = L"Windows.UI.ViewManagement.UIViewSettings";
1326 HSTRING uwpClassId = nullptr;
1328 if (createHString (uwpClassName, (::UINT32) wcslen (uwpClassName), &uwpClassId) != S_OK
1329 || uwpClassId == nullptr)
1330 return;
1332 JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
1333 status = roGetActivationFactory (uwpClassId, __uuidof (IUIViewSettingsInterop),
1334 (void**) viewSettingsInterop.resetAndGetPointerAddress());
1335 JUCE_END_IGNORE_WARNINGS_GCC_LIKE
1336 deleteHString (uwpClassId);
1338 if (status != S_OK || viewSettingsInterop == nullptr)
1339 return;
1341 // move dll into member var
1342 comBaseDLL = std::move (dll);
1346 private:
1347 //==============================================================================
1348 struct ComBaseModule
1350 ComBaseModule() = default;
1351 ComBaseModule (LPCWSTR libraryName) : h (::LoadLibrary (libraryName)) {}
1352 ComBaseModule (ComBaseModule&& o) : h (o.h) { o.h = nullptr; }
1353 ~ComBaseModule() { release(); }
1355 void release() { if (h != nullptr) ::FreeLibrary (h); h = nullptr; }
1356 ComBaseModule& operator= (ComBaseModule&& o) { release(); h = o.h; o.h = nullptr; return *this; }
1358 HMODULE h = {};
1361 using RoInitializeFuncPtr = HRESULT (WINAPI*) (int);
1362 using RoGetActivationFactoryFuncPtr = HRESULT (WINAPI*) (HSTRING, REFIID, void**);
1363 using WindowsCreateStringFuncPtr = HRESULT (WINAPI*) (LPCWSTR,UINT32, HSTRING*);
1364 using WindowsDeleteStringFuncPtr = HRESULT (WINAPI*) (HSTRING);
1366 ComBaseModule comBaseDLL;
1367 ComSmartPtr<IUIViewSettingsInterop> viewSettingsInterop;
1369 RoInitializeFuncPtr roInitialize;
1370 RoGetActivationFactoryFuncPtr roGetActivationFactory;
1371 WindowsCreateStringFuncPtr createHString;
1372 WindowsDeleteStringFuncPtr deleteHString;
1375 //==============================================================================
1376 #if JUCE_MSVC
1377 static HMONITOR getMonitorFromOutput (ComSmartPtr<IDXGIOutput> output)
1379 DXGI_OUTPUT_DESC desc = {};
1380 return (FAILED (output->GetDesc (&desc)) || ! desc.AttachedToDesktop)
1381 ? nullptr
1382 : desc.Monitor;
1385 struct VBlankListener
1387 virtual void onVBlank() = 0;
1390 //==============================================================================
1391 class VSyncThread : private Thread,
1392 private AsyncUpdater
1394 public:
1395 VSyncThread (ComSmartPtr<IDXGIOutput> out,
1396 HMONITOR mon,
1397 VBlankListener& listener)
1398 : Thread ("VSyncThread"),
1399 output (out),
1400 monitor (mon)
1402 listeners.push_back (listener);
1403 startThread (10);
1406 ~VSyncThread() override
1408 stopThread (-1);
1409 cancelPendingUpdate();
1412 void updateMonitor()
1414 monitor = getMonitorFromOutput (output);
1417 HMONITOR getMonitor() const noexcept { return monitor; }
1419 void addListener (VBlankListener& listener)
1421 listeners.push_back (listener);
1424 bool removeListener (const VBlankListener& listener)
1426 auto it = std::find_if (listeners.cbegin(),
1427 listeners.cend(),
1428 [&listener] (const auto& l) { return &(l.get()) == &listener; });
1430 if (it != listeners.cend())
1432 listeners.erase (it);
1433 return true;
1436 return false;
1439 bool hasNoListeners() const noexcept
1441 return listeners.empty();
1444 bool hasListener (const VBlankListener& listener) const noexcept
1446 return std::any_of (listeners.cbegin(),
1447 listeners.cend(),
1448 [&listener] (const auto& l) { return &(l.get()) == &listener; });
1451 private:
1452 //==============================================================================
1453 void run() override
1455 while (! threadShouldExit())
1457 if (output->WaitForVBlank() == S_OK)
1458 triggerAsyncUpdate();
1459 else
1460 Thread::sleep (1);
1464 void handleAsyncUpdate() override
1466 for (auto& listener : listeners)
1467 listener.get().onVBlank();
1470 //==============================================================================
1471 ComSmartPtr<IDXGIOutput> output;
1472 HMONITOR monitor = nullptr;
1473 std::vector<std::reference_wrapper<VBlankListener>> listeners;
1475 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VSyncThread)
1476 JUCE_DECLARE_NON_MOVEABLE (VSyncThread)
1479 //==============================================================================
1480 class VBlankDispatcher : public DeletedAtShutdown
1482 public:
1483 void updateDisplay (VBlankListener& listener, HMONITOR monitor)
1485 if (monitor == nullptr)
1487 removeListener (listener);
1488 return;
1491 auto threadWithListener = threads.end();
1492 auto threadWithMonitor = threads.end();
1494 for (auto it = threads.begin(); it != threads.end(); ++it)
1496 if ((*it)->hasListener (listener))
1497 threadWithListener = it;
1499 if ((*it)->getMonitor() == monitor)
1500 threadWithMonitor = it;
1502 if (threadWithListener != threads.end()
1503 && threadWithMonitor != threads.end())
1505 if (threadWithListener == threadWithMonitor)
1506 return;
1508 (*threadWithMonitor)->addListener (listener);
1510 // This may invalidate iterators, so be careful!
1511 removeListener (threadWithListener, listener);
1512 return;
1516 if (threadWithMonitor != threads.end())
1518 (*threadWithMonitor)->addListener (listener);
1519 return;
1522 if (threadWithListener != threads.end())
1523 removeListener (threadWithListener, listener);
1525 for (auto adapter : adapters)
1527 UINT i = 0;
1528 ComSmartPtr<IDXGIOutput> output;
1530 while (adapter->EnumOutputs (i, output.resetAndGetPointerAddress()) != DXGI_ERROR_NOT_FOUND)
1532 if (getMonitorFromOutput (output) == monitor)
1534 threads.emplace_back (std::make_unique<VSyncThread> (output, monitor, listener));
1535 return;
1538 ++i;
1543 void removeListener (const VBlankListener& listener)
1545 for (auto it = threads.begin(); it != threads.end(); ++it)
1546 if (removeListener (it, listener))
1547 return;
1550 void reconfigureDisplays()
1552 adapters.clear();
1554 ComSmartPtr<IDXGIFactory> factory;
1555 JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
1556 CreateDXGIFactory (__uuidof (IDXGIFactory), (void**)factory.resetAndGetPointerAddress());
1557 JUCE_END_IGNORE_WARNINGS_GCC_LIKE
1559 UINT i = 0;
1560 ComSmartPtr<IDXGIAdapter> adapter;
1562 while (factory->EnumAdapters (i, adapter.resetAndGetPointerAddress()) != DXGI_ERROR_NOT_FOUND)
1564 adapters.push_back (adapter);
1565 ++i;
1568 for (auto& thread : threads)
1569 thread->updateMonitor();
1571 threads.erase (std::remove_if (threads.begin(),
1572 threads.end(),
1573 [] (const auto& thread) { return thread->getMonitor() == nullptr; }),
1574 threads.end());
1577 JUCE_DECLARE_SINGLETON_SINGLETHREADED (VBlankDispatcher, false)
1579 private:
1580 //==============================================================================
1581 using Threads = std::vector<std::unique_ptr<VSyncThread>>;
1583 VBlankDispatcher()
1585 reconfigureDisplays();
1588 ~VBlankDispatcher() override
1590 threads.clear();
1591 clearSingletonInstance();
1594 // This may delete the corresponding thread and invalidate iterators,
1595 // so be careful!
1596 bool removeListener (Threads::iterator it, const VBlankListener& listener)
1598 if ((*it)->removeListener (listener))
1600 if ((*it)->hasNoListeners())
1601 threads.erase (it);
1603 return true;
1606 return false;
1609 //==============================================================================
1610 std::vector<ComSmartPtr<IDXGIAdapter>> adapters;
1611 Threads threads;
1613 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VBlankDispatcher)
1614 JUCE_DECLARE_NON_MOVEABLE (VBlankDispatcher)
1617 JUCE_IMPLEMENT_SINGLETON (VBlankDispatcher)
1618 #endif
1620 //==============================================================================
1621 class HWNDComponentPeer : public ComponentPeer,
1622 #if JUCE_MSVC
1623 private VBlankListener,
1624 #endif
1625 private Timer
1626 #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client
1627 , public ModifierKeyReceiver
1628 #endif
1630 public:
1631 enum RenderingEngineType
1633 softwareRenderingEngine = 0,
1634 direct2DRenderingEngine
1637 //==============================================================================
1638 HWNDComponentPeer (Component& comp, int windowStyleFlags, HWND parent, bool nonRepainting)
1639 : ComponentPeer (comp, windowStyleFlags),
1640 dontRepaint (nonRepainting),
1641 parentToAddTo (parent),
1642 currentRenderingEngine (softwareRenderingEngine)
1644 callFunctionIfNotLocked (&createWindowCallback, this);
1646 setTitle (component.getName());
1647 updateShadower();
1649 getNativeRealtimeModifiers = []
1651 HWNDComponentPeer::updateKeyModifiers();
1653 int mouseMods = 0;
1654 if (HWNDComponentPeer::isKeyDown (VK_LBUTTON)) mouseMods |= ModifierKeys::leftButtonModifier;
1655 if (HWNDComponentPeer::isKeyDown (VK_RBUTTON)) mouseMods |= ModifierKeys::rightButtonModifier;
1656 if (HWNDComponentPeer::isKeyDown (VK_MBUTTON)) mouseMods |= ModifierKeys::middleButtonModifier;
1658 ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons().withFlags (mouseMods);
1660 return ModifierKeys::currentModifiers;
1663 #if JUCE_MSVC
1664 if (updateCurrentMonitor())
1665 VBlankDispatcher::getInstance()->updateDisplay (*this, currentMonitor);
1666 #else
1667 updateCurrentMonitor();
1668 #endif
1671 ~HWNDComponentPeer() override
1673 #if JUCE_MSVC
1674 VBlankDispatcher::getInstance()->removeListener (*this);
1675 #endif
1677 // do this first to avoid messages arriving for this window before it's destroyed
1678 JuceWindowIdentifier::setAsJUCEWindow (hwnd, false);
1680 #if JUCE_MSVC
1681 if (isAccessibilityActive)
1682 WindowsAccessibility::revokeUIAMapEntriesForWindow (hwnd);
1683 #endif
1685 shadower = nullptr;
1686 currentTouches.deleteAllTouchesForPeer (this);
1688 callFunctionIfNotLocked (&destroyWindowCallback, (void*) hwnd);
1690 if (currentWindowIcon != nullptr)
1691 DestroyIcon (currentWindowIcon);
1693 if (dropTarget != nullptr)
1695 dropTarget->peerIsDeleted = true;
1696 dropTarget->Release();
1697 dropTarget = nullptr;
1700 #if JUCE_DIRECT2D
1701 direct2DContext = nullptr;
1702 #endif
1705 //==============================================================================
1706 void* getNativeHandle() const override { return hwnd; }
1708 void setVisible (bool shouldBeVisible) override
1710 const ScopedValueSetter<bool> scope (shouldIgnoreModalDismiss, true);
1712 ShowWindow (hwnd, shouldBeVisible ? SW_SHOWNA : SW_HIDE);
1714 if (shouldBeVisible)
1715 InvalidateRect (hwnd, nullptr, 0);
1716 else
1717 lastPaintTime = 0;
1720 void setTitle (const String& title) override
1722 // Unfortunately some ancient bits of win32 mean you can only perform this operation from the message thread.
1723 JUCE_ASSERT_MESSAGE_THREAD
1725 SetWindowText (hwnd, title.toWideCharPointer());
1728 void repaintNowIfTransparent()
1730 if (isUsingUpdateLayeredWindow() && lastPaintTime > 0 && Time::getMillisecondCounter() > lastPaintTime + 30)
1731 handlePaintMessage();
1734 void updateBorderSize()
1736 WINDOWINFO info;
1737 info.cbSize = sizeof (info);
1739 if (GetWindowInfo (hwnd, &info))
1740 windowBorder = BorderSize<int> (roundToInt ((info.rcClient.top - info.rcWindow.top) / scaleFactor),
1741 roundToInt ((info.rcClient.left - info.rcWindow.left) / scaleFactor),
1742 roundToInt ((info.rcWindow.bottom - info.rcClient.bottom) / scaleFactor),
1743 roundToInt ((info.rcWindow.right - info.rcClient.right) / scaleFactor));
1745 #if JUCE_DIRECT2D
1746 if (direct2DContext != nullptr)
1747 direct2DContext->resized();
1748 #endif
1751 void setBounds (const Rectangle<int>& bounds, bool isNowFullScreen) override
1753 // If we try to set new bounds while handling an existing position change,
1754 // Windows may get confused about our current scale and size.
1755 // This can happen when moving a window between displays, because the mouse-move
1756 // generator in handlePositionChanged can cause the window to move again.
1757 if (inHandlePositionChanged)
1758 return;
1760 const ScopedValueSetter<bool> scope (shouldIgnoreModalDismiss, true);
1762 fullScreen = isNowFullScreen;
1764 auto newBounds = windowBorder.addedTo (bounds);
1766 if (isUsingUpdateLayeredWindow())
1768 if (auto parentHwnd = GetParent (hwnd))
1770 auto parentRect = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (parentHwnd)), hwnd);
1771 newBounds.translate (parentRect.getX(), parentRect.getY());
1775 auto oldBounds = getBounds();
1777 const bool hasMoved = (oldBounds.getPosition() != bounds.getPosition());
1778 const bool hasResized = (oldBounds.getWidth() != bounds.getWidth()
1779 || oldBounds.getHeight() != bounds.getHeight());
1781 DWORD flags = SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER;
1782 if (! hasMoved) flags |= SWP_NOMOVE;
1783 if (! hasResized) flags |= SWP_NOSIZE;
1785 setWindowPos (hwnd, newBounds, flags, ! inDpiChange);
1787 if (hasResized && isValidPeer (this))
1789 updateBorderSize();
1790 repaintNowIfTransparent();
1794 Rectangle<int> getBounds() const override
1796 auto bounds = [this]
1798 if (parentToAddTo == nullptr)
1799 return convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (hwnd)), hwnd);
1801 auto localBounds = rectangleFromRECT (getWindowClientRect (hwnd));
1803 if (isPerMonitorDPIAwareWindow (hwnd))
1804 return (localBounds.toDouble() / getPlatformScaleFactor()).toNearestInt();
1806 return localBounds;
1807 }();
1809 return windowBorder.subtractedFrom (bounds);
1812 Point<int> getScreenPosition() const
1814 auto r = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (hwnd)), hwnd);
1816 return { r.getX() + windowBorder.getLeft(),
1817 r.getY() + windowBorder.getTop() };
1820 Point<float> localToGlobal (Point<float> relativePosition) override { return relativePosition + getScreenPosition().toFloat(); }
1821 Point<float> globalToLocal (Point<float> screenPosition) override { return screenPosition - getScreenPosition().toFloat(); }
1823 using ComponentPeer::localToGlobal;
1824 using ComponentPeer::globalToLocal;
1826 void setAlpha (float newAlpha) override
1828 const ScopedValueSetter<bool> scope (shouldIgnoreModalDismiss, true);
1830 auto intAlpha = (uint8) jlimit (0, 255, (int) (newAlpha * 255.0f));
1832 if (component.isOpaque())
1834 if (newAlpha < 1.0f)
1836 SetWindowLong (hwnd, GWL_EXSTYLE, GetWindowLong (hwnd, GWL_EXSTYLE) | WS_EX_LAYERED);
1837 SetLayeredWindowAttributes (hwnd, RGB (0, 0, 0), intAlpha, LWA_ALPHA);
1839 else
1841 SetWindowLong (hwnd, GWL_EXSTYLE, GetWindowLong (hwnd, GWL_EXSTYLE) & ~WS_EX_LAYERED);
1842 RedrawWindow (hwnd, nullptr, nullptr, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN);
1845 else
1847 updateLayeredWindowAlpha = intAlpha;
1848 component.repaint();
1852 void setMinimised (bool shouldBeMinimised) override
1854 const ScopedValueSetter<bool> scope (shouldIgnoreModalDismiss, true);
1856 if (shouldBeMinimised != isMinimised())
1857 ShowWindow (hwnd, shouldBeMinimised ? SW_MINIMIZE : SW_RESTORE);
1860 bool isMinimised() const override
1862 WINDOWPLACEMENT wp;
1863 wp.length = sizeof (WINDOWPLACEMENT);
1864 GetWindowPlacement (hwnd, &wp);
1866 return wp.showCmd == SW_SHOWMINIMIZED;
1869 void setFullScreen (bool shouldBeFullScreen) override
1871 const ScopedValueSetter<bool> scope (shouldIgnoreModalDismiss, true);
1873 setMinimised (false);
1875 if (isFullScreen() != shouldBeFullScreen)
1877 if (constrainer != nullptr)
1878 constrainer->resizeStart();
1880 fullScreen = shouldBeFullScreen;
1881 const WeakReference<Component> deletionChecker (&component);
1883 if (! fullScreen)
1885 auto boundsCopy = lastNonFullscreenBounds;
1887 if (hasTitleBar())
1888 ShowWindow (hwnd, SW_SHOWNORMAL);
1890 if (! boundsCopy.isEmpty())
1891 setBounds (ScalingHelpers::scaledScreenPosToUnscaled (component, boundsCopy), false);
1893 else
1895 if (hasTitleBar())
1896 ShowWindow (hwnd, SW_SHOWMAXIMIZED);
1897 else
1898 SendMessageW (hwnd, WM_SETTINGCHANGE, 0, 0);
1901 if (deletionChecker != nullptr)
1902 handleMovedOrResized();
1904 if (constrainer != nullptr)
1905 constrainer->resizeEnd();
1909 bool isFullScreen() const override
1911 if (! hasTitleBar())
1912 return fullScreen;
1914 WINDOWPLACEMENT wp;
1915 wp.length = sizeof (wp);
1916 GetWindowPlacement (hwnd, &wp);
1918 return wp.showCmd == SW_SHOWMAXIMIZED;
1921 bool contains (Point<int> localPos, bool trueIfInAChildWindow) const override
1923 auto r = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (hwnd)), hwnd);
1925 if (! r.withZeroOrigin().contains (localPos))
1926 return false;
1928 auto w = WindowFromPoint (POINTFromPoint (convertLogicalScreenPointToPhysical (localPos + getScreenPosition(),
1929 hwnd)));
1931 return w == hwnd || (trueIfInAChildWindow && (IsChild (hwnd, w) != 0));
1934 OptionalBorderSize getFrameSizeIfPresent() const override
1936 return ComponentPeer::OptionalBorderSize { windowBorder };
1939 BorderSize<int> getFrameSize() const override
1941 return windowBorder;
1944 bool setAlwaysOnTop (bool alwaysOnTop) override
1946 const bool oldDeactivate = shouldDeactivateTitleBar;
1947 shouldDeactivateTitleBar = ((styleFlags & windowIsTemporary) == 0);
1949 setWindowZOrder (hwnd, alwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST);
1951 shouldDeactivateTitleBar = oldDeactivate;
1953 if (shadower != nullptr)
1954 handleBroughtToFront();
1956 return true;
1959 void toFront (bool makeActive) override
1961 const ScopedValueSetter<bool> scope (shouldIgnoreModalDismiss, true);
1963 setMinimised (false);
1965 const bool oldDeactivate = shouldDeactivateTitleBar;
1966 shouldDeactivateTitleBar = ((styleFlags & windowIsTemporary) == 0);
1968 callFunctionIfNotLocked (makeActive ? &toFrontCallback1 : &toFrontCallback2, hwnd);
1970 shouldDeactivateTitleBar = oldDeactivate;
1972 if (! makeActive)
1974 // in this case a broughttofront call won't have occurred, so do it now..
1975 handleBroughtToFront();
1979 void toBehind (ComponentPeer* other) override
1981 const ScopedValueSetter<bool> scope (shouldIgnoreModalDismiss, true);
1983 if (auto* otherPeer = dynamic_cast<HWNDComponentPeer*> (other))
1985 setMinimised (false);
1987 // Must be careful not to try to put a topmost window behind a normal one, or Windows
1988 // promotes the normal one to be topmost!
1989 if (component.isAlwaysOnTop() == otherPeer->getComponent().isAlwaysOnTop())
1990 setWindowZOrder (hwnd, otherPeer->hwnd);
1991 else if (otherPeer->getComponent().isAlwaysOnTop())
1992 setWindowZOrder (hwnd, HWND_TOP);
1994 else
1996 jassertfalse; // wrong type of window?
2000 bool isFocused() const override
2002 return callFunctionIfNotLocked (&getFocusCallback, nullptr) == (void*) hwnd;
2005 void grabFocus() override
2007 const ScopedValueSetter<bool> scope (shouldIgnoreModalDismiss, true);
2009 const bool oldDeactivate = shouldDeactivateTitleBar;
2010 shouldDeactivateTitleBar = ((styleFlags & windowIsTemporary) == 0);
2012 callFunctionIfNotLocked (&setFocusCallback, hwnd);
2014 shouldDeactivateTitleBar = oldDeactivate;
2017 void textInputRequired (Point<int>, TextInputTarget&) override
2019 if (! hasCreatedCaret)
2020 hasCreatedCaret = CreateCaret (hwnd, (HBITMAP) 1, 0, 0);
2022 if (hasCreatedCaret)
2024 SetCaretPos (0, 0);
2025 ShowCaret (hwnd);
2028 ImmAssociateContext (hwnd, nullptr);
2030 // MSVC complains about the nullptr argument, but the docs for this
2031 // function say that the second argument is ignored when the third
2032 // argument is IACE_DEFAULT.
2033 JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6387)
2034 ImmAssociateContextEx (hwnd, nullptr, IACE_DEFAULT);
2035 JUCE_END_IGNORE_WARNINGS_MSVC
2038 void closeInputMethodContext() override
2040 imeHandler.handleSetContext (hwnd, false);
2043 void dismissPendingTextInput() override
2045 closeInputMethodContext();
2047 ImmAssociateContext (hwnd, nullptr);
2049 if (std::exchange (hasCreatedCaret, false))
2050 DestroyCaret();
2053 void repaint (const Rectangle<int>& area) override
2055 deferredRepaints.add ((area.toDouble() * getPlatformScaleFactor()).getSmallestIntegerContainer());
2058 void dispatchDeferredRepaints()
2060 for (auto deferredRect : deferredRepaints)
2062 auto r = RECTFromRectangle (deferredRect);
2063 InvalidateRect (hwnd, &r, FALSE);
2066 deferredRepaints.clear();
2069 void performAnyPendingRepaintsNow() override
2071 if (component.isVisible())
2073 dispatchDeferredRepaints();
2075 WeakReference<Component> localRef (&component);
2076 MSG m;
2078 if (isUsingUpdateLayeredWindow() || PeekMessage (&m, hwnd, WM_PAINT, WM_PAINT, PM_REMOVE))
2079 if (localRef != nullptr) // (the PeekMessage call can dispatch messages, which may delete this comp)
2080 handlePaintMessage();
2084 //==============================================================================
2085 #if JUCE_MSVC
2086 void onVBlank() override
2088 dispatchDeferredRepaints();
2090 #endif
2092 //==============================================================================
2093 static HWNDComponentPeer* getOwnerOfWindow (HWND h) noexcept
2095 if (h != nullptr && JuceWindowIdentifier::isJUCEWindow (h))
2096 return (HWNDComponentPeer*) GetWindowLongPtr (h, 8);
2098 return nullptr;
2101 //==============================================================================
2102 bool isInside (HWND h) const noexcept
2104 return GetAncestor (hwnd, GA_ROOT) == h;
2107 //==============================================================================
2108 static bool isKeyDown (const int key) noexcept { return (GetAsyncKeyState (key) & 0x8000) != 0; }
2110 static void updateKeyModifiers() noexcept
2112 int keyMods = 0;
2113 if (isKeyDown (VK_SHIFT)) keyMods |= ModifierKeys::shiftModifier;
2114 if (isKeyDown (VK_CONTROL)) keyMods |= ModifierKeys::ctrlModifier;
2115 if (isKeyDown (VK_MENU)) keyMods |= ModifierKeys::altModifier;
2117 // workaround: Windows maps AltGr to left-Ctrl + right-Alt.
2118 if (isKeyDown (VK_RMENU) && !isKeyDown (VK_RCONTROL))
2120 keyMods = (keyMods & ~ModifierKeys::ctrlModifier) | ModifierKeys::altModifier;
2123 ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withOnlyMouseButtons().withFlags (keyMods);
2126 static void updateModifiersFromWParam (const WPARAM wParam)
2128 int mouseMods = 0;
2129 if (wParam & MK_LBUTTON) mouseMods |= ModifierKeys::leftButtonModifier;
2130 if (wParam & MK_RBUTTON) mouseMods |= ModifierKeys::rightButtonModifier;
2131 if (wParam & MK_MBUTTON) mouseMods |= ModifierKeys::middleButtonModifier;
2133 ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons().withFlags (mouseMods);
2134 updateKeyModifiers();
2137 //==============================================================================
2138 bool dontRepaint;
2139 static ModifierKeys modifiersAtLastCallback;
2141 //==============================================================================
2142 struct FileDropTarget : public ComBaseClassHelper<IDropTarget>
2144 FileDropTarget (HWNDComponentPeer& p) : peer (p) {}
2146 JUCE_COMRESULT DragEnter (IDataObject* pDataObject, DWORD grfKeyState, POINTL mousePos, DWORD* pdwEffect) override
2148 auto hr = updateFileList (pDataObject);
2150 if (FAILED (hr))
2151 return hr;
2153 return DragOver (grfKeyState, mousePos, pdwEffect);
2156 JUCE_COMRESULT DragLeave() override
2158 if (peerIsDeleted)
2159 return S_FALSE;
2161 peer.handleDragExit (dragInfo);
2162 return S_OK;
2165 JUCE_COMRESULT DragOver (DWORD /*grfKeyState*/, POINTL mousePos, DWORD* pdwEffect) override
2167 if (peerIsDeleted)
2168 return S_FALSE;
2170 dragInfo.position = getMousePos (mousePos).roundToInt();
2171 *pdwEffect = peer.handleDragMove (dragInfo) ? (DWORD) DROPEFFECT_COPY
2172 : (DWORD) DROPEFFECT_NONE;
2173 return S_OK;
2176 JUCE_COMRESULT Drop (IDataObject* pDataObject, DWORD /*grfKeyState*/, POINTL mousePos, DWORD* pdwEffect) override
2178 auto hr = updateFileList (pDataObject);
2180 if (FAILED (hr))
2181 return hr;
2183 dragInfo.position = getMousePos (mousePos).roundToInt();
2184 *pdwEffect = peer.handleDragDrop (dragInfo) ? (DWORD) DROPEFFECT_COPY
2185 : (DWORD) DROPEFFECT_NONE;
2186 return S_OK;
2189 HWNDComponentPeer& peer;
2190 ComponentPeer::DragInfo dragInfo;
2191 bool peerIsDeleted = false;
2193 private:
2194 Point<float> getMousePos (POINTL mousePos) const
2196 const auto originalPos = pointFromPOINT ({ mousePos.x, mousePos.y });
2197 const auto logicalPos = convertPhysicalScreenPointToLogical (originalPos, peer.hwnd);
2198 return ScalingHelpers::screenPosToLocalPos (peer.component, logicalPos.toFloat());
2201 struct DroppedData
2203 DroppedData (IDataObject* dataObject, CLIPFORMAT type)
2205 FORMATETC format = { type, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
2207 if (SUCCEEDED (error = dataObject->GetData (&format, &medium)) && medium.hGlobal != nullptr)
2209 dataSize = GlobalSize (medium.hGlobal);
2210 data = GlobalLock (medium.hGlobal);
2214 ~DroppedData()
2216 if (data != nullptr && medium.hGlobal != nullptr)
2217 GlobalUnlock (medium.hGlobal);
2220 HRESULT error;
2221 STGMEDIUM medium { TYMED_HGLOBAL, { nullptr }, nullptr };
2222 void* data = {};
2223 SIZE_T dataSize;
2226 void parseFileList (HDROP dropFiles)
2228 dragInfo.files.clearQuick();
2230 std::vector<TCHAR> nameBuffer;
2232 const auto numFiles = DragQueryFile (dropFiles, ~(UINT) 0, nullptr, 0);
2234 for (UINT i = 0; i < numFiles; ++i)
2236 const auto bufferSize = DragQueryFile (dropFiles, i, nullptr, 0);
2237 nameBuffer.clear();
2238 nameBuffer.resize (bufferSize + 1, 0); // + 1 for the null terminator
2240 const auto readCharacters = DragQueryFile (dropFiles, i, nameBuffer.data(), (UINT) nameBuffer.size());
2241 ignoreUnused (readCharacters);
2242 jassert (readCharacters == bufferSize);
2244 dragInfo.files.add (String (nameBuffer.data()));
2248 HRESULT updateFileList (IDataObject* const dataObject)
2250 if (peerIsDeleted)
2251 return S_FALSE;
2253 dragInfo.clear();
2256 DroppedData fileData (dataObject, CF_HDROP);
2258 if (SUCCEEDED (fileData.error))
2260 parseFileList (static_cast<HDROP> (fileData.data));
2261 return S_OK;
2265 DroppedData textData (dataObject, CF_UNICODETEXT);
2267 if (SUCCEEDED (textData.error))
2269 dragInfo.text = String (CharPointer_UTF16 ((const WCHAR*) textData.data),
2270 CharPointer_UTF16 ((const WCHAR*) addBytesToPointer (textData.data, textData.dataSize)));
2271 return S_OK;
2274 return textData.error;
2277 JUCE_DECLARE_NON_COPYABLE (FileDropTarget)
2280 static bool offerKeyMessageToJUCEWindow (MSG& m)
2282 if (m.message == WM_KEYDOWN || m.message == WM_KEYUP)
2284 if (Component::getCurrentlyFocusedComponent() != nullptr)
2286 if (auto* peer = getOwnerOfWindow (m.hwnd))
2288 ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { m.hwnd };
2290 return m.message == WM_KEYDOWN ? peer->doKeyDown (m.wParam)
2291 : peer->doKeyUp (m.wParam);
2296 return false;
2299 double getPlatformScaleFactor() const noexcept override
2301 #if ! JUCE_WIN_PER_MONITOR_DPI_AWARE
2302 return 1.0;
2303 #else
2304 if (! isPerMonitorDPIAwareWindow (hwnd))
2305 return 1.0;
2307 if (auto* parentHWND = GetParent (hwnd))
2309 if (auto* parentPeer = getOwnerOfWindow (parentHWND))
2310 return parentPeer->getPlatformScaleFactor();
2312 if (getDPIForWindow != nullptr)
2313 return getScaleFactorForWindow (parentHWND);
2316 return scaleFactor;
2317 #endif
2320 private:
2321 HWND hwnd, parentToAddTo;
2322 std::unique_ptr<DropShadower> shadower;
2323 RenderingEngineType currentRenderingEngine;
2324 #if JUCE_DIRECT2D
2325 std::unique_ptr<Direct2DLowLevelGraphicsContext> direct2DContext;
2326 #endif
2327 uint32 lastPaintTime = 0;
2328 ULONGLONG lastMagnifySize = 0;
2329 bool fullScreen = false, isDragging = false, isMouseOver = false,
2330 hasCreatedCaret = false, constrainerIsResizing = false;
2331 BorderSize<int> windowBorder;
2332 HICON currentWindowIcon = nullptr;
2333 FileDropTarget* dropTarget = nullptr;
2334 uint8 updateLayeredWindowAlpha = 255;
2335 UWPUIViewSettings uwpViewSettings;
2336 #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client
2337 ModifierKeyProvider* modProvider = nullptr;
2338 #endif
2340 double scaleFactor = 1.0;
2341 bool inDpiChange = 0, inHandlePositionChanged = 0;
2342 HMONITOR currentMonitor = nullptr;
2344 bool isAccessibilityActive = false;
2346 //==============================================================================
2347 static MultiTouchMapper<DWORD> currentTouches;
2349 //==============================================================================
2350 struct TemporaryImage : private Timer
2352 TemporaryImage() {}
2354 Image& getImage (bool transparent, int w, int h)
2356 auto format = transparent ? Image::ARGB : Image::RGB;
2358 if ((! image.isValid()) || image.getWidth() < w || image.getHeight() < h || image.getFormat() != format)
2359 image = Image (new WindowsBitmapImage (format, (w + 31) & ~31, (h + 31) & ~31, false));
2361 startTimer (3000);
2362 return image;
2365 void timerCallback() override
2367 stopTimer();
2368 image = {};
2371 private:
2372 Image image;
2374 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TemporaryImage)
2377 TemporaryImage offscreenImageGenerator;
2379 //==============================================================================
2380 class WindowClassHolder : private DeletedAtShutdown
2382 public:
2383 WindowClassHolder()
2385 // this name has to be different for each app/dll instance because otherwise poor old Windows can
2386 // get a bit confused (even despite it not being a process-global window class).
2387 String windowClassName ("JUCE_");
2388 windowClassName << String::toHexString (Time::currentTimeMillis());
2390 auto moduleHandle = (HINSTANCE) Process::getCurrentModuleInstanceHandle();
2392 TCHAR moduleFile[1024] = {};
2393 GetModuleFileName (moduleHandle, moduleFile, 1024);
2394 WORD iconNum = 0;
2396 WNDCLASSEX wcex = {};
2397 wcex.cbSize = sizeof (wcex);
2398 wcex.style = CS_OWNDC;
2399 wcex.lpfnWndProc = (WNDPROC) windowProc;
2400 wcex.lpszClassName = windowClassName.toWideCharPointer();
2401 wcex.cbWndExtra = 32;
2402 wcex.hInstance = moduleHandle;
2403 wcex.hIcon = ExtractAssociatedIcon (moduleHandle, moduleFile, &iconNum);
2404 iconNum = 1;
2405 wcex.hIconSm = ExtractAssociatedIcon (moduleHandle, moduleFile, &iconNum);
2407 atom = RegisterClassEx (&wcex);
2408 jassert (atom != 0);
2410 isEventBlockedByModalComps = checkEventBlockedByModalComps;
2413 ~WindowClassHolder()
2415 if (ComponentPeer::getNumPeers() == 0)
2416 UnregisterClass (getWindowClassName(), (HINSTANCE) Process::getCurrentModuleInstanceHandle());
2418 clearSingletonInstance();
2421 LPCTSTR getWindowClassName() const noexcept { return (LPCTSTR) (pointer_sized_uint) atom; }
2423 JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (WindowClassHolder)
2425 private:
2426 ATOM atom;
2428 static bool isHWNDBlockedByModalComponents (HWND h)
2430 for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
2431 if (auto* c = Desktop::getInstance().getComponent (i))
2432 if ((! c->isCurrentlyBlockedByAnotherModalComponent())
2433 && IsChild ((HWND) c->getWindowHandle(), h))
2434 return false;
2436 return true;
2439 static bool checkEventBlockedByModalComps (const MSG& m)
2441 if (Component::getNumCurrentlyModalComponents() == 0 || JuceWindowIdentifier::isJUCEWindow (m.hwnd))
2442 return false;
2444 switch (m.message)
2446 case WM_MOUSEMOVE:
2447 case WM_NCMOUSEMOVE:
2448 case 0x020A: /* WM_MOUSEWHEEL */
2449 case 0x020E: /* WM_MOUSEHWHEEL */
2450 case WM_KEYUP:
2451 case WM_SYSKEYUP:
2452 case WM_CHAR:
2453 case WM_APPCOMMAND:
2454 case WM_LBUTTONUP:
2455 case WM_MBUTTONUP:
2456 case WM_RBUTTONUP:
2457 case WM_MOUSEACTIVATE:
2458 case WM_NCMOUSEHOVER:
2459 case WM_MOUSEHOVER:
2460 case WM_TOUCH:
2461 case WM_POINTERUPDATE:
2462 case WM_NCPOINTERUPDATE:
2463 case WM_POINTERWHEEL:
2464 case WM_POINTERHWHEEL:
2465 case WM_POINTERUP:
2466 case WM_POINTERACTIVATE:
2467 return isHWNDBlockedByModalComponents(m.hwnd);
2468 case WM_NCLBUTTONDOWN:
2469 case WM_NCLBUTTONDBLCLK:
2470 case WM_NCRBUTTONDOWN:
2471 case WM_NCRBUTTONDBLCLK:
2472 case WM_NCMBUTTONDOWN:
2473 case WM_NCMBUTTONDBLCLK:
2474 case WM_LBUTTONDOWN:
2475 case WM_LBUTTONDBLCLK:
2476 case WM_MBUTTONDOWN:
2477 case WM_MBUTTONDBLCLK:
2478 case WM_RBUTTONDOWN:
2479 case WM_RBUTTONDBLCLK:
2480 case WM_KEYDOWN:
2481 case WM_SYSKEYDOWN:
2482 case WM_NCPOINTERDOWN:
2483 case WM_POINTERDOWN:
2484 if (isHWNDBlockedByModalComponents (m.hwnd))
2486 if (auto* modal = Component::getCurrentlyModalComponent (0))
2487 modal->inputAttemptWhenModal();
2489 return true;
2491 break;
2493 default:
2494 break;
2497 return false;
2500 JUCE_DECLARE_NON_COPYABLE (WindowClassHolder)
2503 //==============================================================================
2504 static void* createWindowCallback (void* userData)
2506 static_cast<HWNDComponentPeer*> (userData)->createWindow();
2507 return nullptr;
2510 void createWindow()
2512 DWORD exstyle = 0;
2513 DWORD type = WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
2515 if (hasTitleBar())
2517 type |= WS_OVERLAPPED;
2519 if ((styleFlags & windowHasCloseButton) != 0)
2521 type |= WS_SYSMENU;
2523 else
2525 // annoyingly, windows won't let you have a min/max button without a close button
2526 jassert ((styleFlags & (windowHasMinimiseButton | windowHasMaximiseButton)) == 0);
2529 if ((styleFlags & windowIsResizable) != 0)
2530 type |= WS_THICKFRAME;
2532 else if (parentToAddTo != nullptr)
2534 type |= WS_CHILD;
2536 else
2538 type |= WS_POPUP | WS_SYSMENU;
2541 if ((styleFlags & windowAppearsOnTaskbar) == 0)
2542 exstyle |= WS_EX_TOOLWINDOW;
2543 else
2544 exstyle |= WS_EX_APPWINDOW;
2546 if ((styleFlags & windowHasMinimiseButton) != 0) type |= WS_MINIMIZEBOX;
2547 if ((styleFlags & windowHasMaximiseButton) != 0) type |= WS_MAXIMIZEBOX;
2548 if ((styleFlags & windowIgnoresMouseClicks) != 0) exstyle |= WS_EX_TRANSPARENT;
2549 if ((styleFlags & windowIsSemiTransparent) != 0) exstyle |= WS_EX_LAYERED;
2551 hwnd = CreateWindowEx (exstyle, WindowClassHolder::getInstance()->getWindowClassName(),
2552 L"", type, 0, 0, 0, 0, parentToAddTo, nullptr,
2553 (HINSTANCE) Process::getCurrentModuleInstanceHandle(), nullptr);
2555 #if JUCE_DEBUG
2556 // The DPI-awareness context of this window and JUCE's hidden message window are different.
2557 // You normally want these to match otherwise timer events and async messages will happen
2558 // in a different context to normal HWND messages which can cause issues with UI scaling.
2559 jassert (isPerMonitorDPIAwareWindow (hwnd) == isPerMonitorDPIAwareWindow (juce_messageWindowHandle)
2560 || isInScopedDPIAwarenessDisabler());
2561 #endif
2563 if (hwnd != nullptr)
2565 SetWindowLongPtr (hwnd, 0, 0);
2566 SetWindowLongPtr (hwnd, 8, (LONG_PTR) this);
2567 JuceWindowIdentifier::setAsJUCEWindow (hwnd, true);
2569 if (dropTarget == nullptr)
2571 HWNDComponentPeer* peer = nullptr;
2573 if (dontRepaint)
2574 peer = getOwnerOfWindow (parentToAddTo);
2576 if (peer == nullptr)
2577 peer = this;
2579 dropTarget = new FileDropTarget (*peer);
2582 RegisterDragDrop (hwnd, dropTarget);
2584 if (canUseMultiTouch())
2585 registerTouchWindow (hwnd, 0);
2587 setDPIAwareness();
2589 if (isPerMonitorDPIAwareThread())
2590 scaleFactor = getScaleFactorForWindow (hwnd);
2592 setMessageFilter();
2593 updateBorderSize();
2594 checkForPointerAPI();
2596 // This is needed so that our plugin window gets notified of WM_SETTINGCHANGE messages
2597 // and can respond to display scale changes
2598 if (! JUCEApplication::isStandaloneApp())
2599 settingChangeCallback = ComponentPeer::forceDisplayUpdate;
2601 // Calling this function here is (for some reason) necessary to make Windows
2602 // correctly enable the menu items that we specify in the wm_initmenu message.
2603 GetSystemMenu (hwnd, false);
2605 auto alpha = component.getAlpha();
2606 if (alpha < 1.0f)
2607 setAlpha (alpha);
2609 else
2611 TCHAR messageBuffer[256] = {};
2613 FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
2614 nullptr, GetLastError(), MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
2615 messageBuffer, (DWORD) numElementsInArray (messageBuffer) - 1, nullptr);
2617 DBG (messageBuffer);
2618 jassertfalse;
2622 static BOOL CALLBACK revokeChildDragDropCallback (HWND hwnd, LPARAM) { RevokeDragDrop (hwnd); return TRUE; }
2624 static void* destroyWindowCallback (void* handle)
2626 auto hwnd = reinterpret_cast<HWND> (handle);
2628 if (IsWindow (hwnd))
2630 RevokeDragDrop (hwnd);
2632 // NB: we need to do this before DestroyWindow() as child HWNDs will be invalid after
2633 EnumChildWindows (hwnd, revokeChildDragDropCallback, 0);
2635 DestroyWindow (hwnd);
2638 return nullptr;
2641 static void* toFrontCallback1 (void* h)
2643 BringWindowToTop ((HWND) h);
2644 return nullptr;
2647 static void* toFrontCallback2 (void* h)
2649 setWindowZOrder ((HWND) h, HWND_TOP);
2650 return nullptr;
2653 static void* setFocusCallback (void* h)
2655 SetFocus ((HWND) h);
2656 return nullptr;
2659 static void* getFocusCallback (void*)
2661 return GetFocus();
2664 bool isUsingUpdateLayeredWindow() const
2666 return ! component.isOpaque();
2669 bool hasTitleBar() const noexcept { return (styleFlags & windowHasTitleBar) != 0; }
2671 void updateShadower()
2673 if (! component.isCurrentlyModal() && (styleFlags & windowHasDropShadow) != 0
2674 && ((! hasTitleBar()) || SystemStats::getOperatingSystemType() < SystemStats::WinVista))
2676 shadower = component.getLookAndFeel().createDropShadowerForComponent (component);
2678 if (shadower != nullptr)
2679 shadower->setOwner (&component);
2683 void setIcon (const Image& newIcon) override
2685 if (auto hicon = IconConverters::createHICONFromImage (newIcon, TRUE, 0, 0))
2687 SendMessage (hwnd, WM_SETICON, ICON_BIG, (LPARAM) hicon);
2688 SendMessage (hwnd, WM_SETICON, ICON_SMALL, (LPARAM) hicon);
2690 if (currentWindowIcon != nullptr)
2691 DestroyIcon (currentWindowIcon);
2693 currentWindowIcon = hicon;
2697 void setMessageFilter()
2699 using ChangeWindowMessageFilterExFunc = BOOL (WINAPI*) (HWND, UINT, DWORD, PVOID);
2701 if (auto changeMessageFilter = (ChangeWindowMessageFilterExFunc) getUser32Function ("ChangeWindowMessageFilterEx"))
2703 changeMessageFilter (hwnd, WM_DROPFILES, 1 /*MSGFLT_ALLOW*/, nullptr);
2704 changeMessageFilter (hwnd, WM_COPYDATA, 1 /*MSGFLT_ALLOW*/, nullptr);
2705 changeMessageFilter (hwnd, 0x49, 1 /*MSGFLT_ALLOW*/, nullptr);
2709 struct ChildWindowClippingInfo
2711 HDC dc;
2712 HWNDComponentPeer* peer;
2713 RectangleList<int>* clip;
2714 Point<int> origin;
2715 int savedDC;
2718 static BOOL CALLBACK clipChildWindowCallback (HWND hwnd, LPARAM context)
2720 if (IsWindowVisible (hwnd))
2722 auto& info = *(ChildWindowClippingInfo*) context;
2724 if (GetParent (hwnd) == info.peer->hwnd)
2726 auto clip = rectangleFromRECT (getWindowClientRect (hwnd));
2728 info.clip->subtract (clip - info.origin);
2730 if (info.savedDC == 0)
2731 info.savedDC = SaveDC (info.dc);
2733 ExcludeClipRect (info.dc, clip.getX(), clip.getY(), clip.getRight(), clip.getBottom());
2737 return TRUE;
2740 //==============================================================================
2741 void handlePaintMessage()
2743 #if JUCE_DIRECT2D
2744 if (direct2DContext != nullptr)
2746 RECT r;
2748 if (GetUpdateRect (hwnd, &r, false))
2750 direct2DContext->start();
2751 direct2DContext->clipToRectangle (convertPhysicalScreenRectangleToLogical (rectangleFromRECT (r), hwnd));
2752 handlePaint (*direct2DContext);
2753 direct2DContext->end();
2754 ValidateRect (hwnd, &r);
2757 else
2758 #endif
2760 HRGN rgn = CreateRectRgn (0, 0, 0, 0);
2761 const int regionType = GetUpdateRgn (hwnd, rgn, false);
2763 PAINTSTRUCT paintStruct;
2764 HDC dc = BeginPaint (hwnd, &paintStruct); // Note this can immediately generate a WM_NCPAINT
2765 // message and become re-entrant, but that's OK
2767 // if something in a paint handler calls, e.g. a message box, this can become reentrant and
2768 // corrupt the image it's using to paint into, so do a check here.
2769 static bool reentrant = false;
2771 if (! reentrant)
2773 const ScopedValueSetter<bool> setter (reentrant, true, false);
2775 if (dontRepaint)
2776 component.handleCommandMessage (0); // (this triggers a repaint in the openGL context)
2777 else
2778 performPaint (dc, rgn, regionType, paintStruct);
2781 DeleteObject (rgn);
2782 EndPaint (hwnd, &paintStruct);
2784 #if JUCE_MSVC
2785 _fpreset(); // because some graphics cards can unmask FP exceptions
2786 #endif
2790 lastPaintTime = Time::getMillisecondCounter();
2793 void performPaint (HDC dc, HRGN rgn, int regionType, PAINTSTRUCT& paintStruct)
2795 int x = paintStruct.rcPaint.left;
2796 int y = paintStruct.rcPaint.top;
2797 int w = paintStruct.rcPaint.right - x;
2798 int h = paintStruct.rcPaint.bottom - y;
2800 const bool transparent = isUsingUpdateLayeredWindow();
2802 if (transparent)
2804 // it's not possible to have a transparent window with a title bar at the moment!
2805 jassert (! hasTitleBar());
2807 auto r = getWindowScreenRect (hwnd);
2808 x = y = 0;
2809 w = r.right - r.left;
2810 h = r.bottom - r.top;
2813 if (w > 0 && h > 0)
2815 Image& offscreenImage = offscreenImageGenerator.getImage (transparent, w, h);
2817 RectangleList<int> contextClip;
2818 const Rectangle<int> clipBounds (w, h);
2820 bool needToPaintAll = true;
2822 if (regionType == COMPLEXREGION && ! transparent)
2824 HRGN clipRgn = CreateRectRgnIndirect (&paintStruct.rcPaint);
2825 CombineRgn (rgn, rgn, clipRgn, RGN_AND);
2826 DeleteObject (clipRgn);
2828 std::aligned_storage<8192, alignof (RGNDATA)>::type rgnData;
2829 const DWORD res = GetRegionData (rgn, sizeof (rgnData), (RGNDATA*) &rgnData);
2831 if (res > 0 && res <= sizeof (rgnData))
2833 const RGNDATAHEADER* const hdr = &(((const RGNDATA*) &rgnData)->rdh);
2835 if (hdr->iType == RDH_RECTANGLES
2836 && hdr->rcBound.right - hdr->rcBound.left >= w
2837 && hdr->rcBound.bottom - hdr->rcBound.top >= h)
2839 needToPaintAll = false;
2841 auto rects = unalignedPointerCast<const RECT*> ((char*) &rgnData + sizeof (RGNDATAHEADER));
2843 for (int i = (int) ((RGNDATA*) &rgnData)->rdh.nCount; --i >= 0;)
2845 if (rects->right <= x + w && rects->bottom <= y + h)
2847 const int cx = jmax (x, (int) rects->left);
2848 contextClip.addWithoutMerging (Rectangle<int> (cx - x, rects->top - y,
2849 rects->right - cx, rects->bottom - rects->top)
2850 .getIntersection (clipBounds));
2852 else
2854 needToPaintAll = true;
2855 break;
2858 ++rects;
2864 if (needToPaintAll)
2866 contextClip.clear();
2867 contextClip.addWithoutMerging (Rectangle<int> (w, h));
2870 ChildWindowClippingInfo childClipInfo = { dc, this, &contextClip, Point<int> (x, y), 0 };
2871 EnumChildWindows (hwnd, clipChildWindowCallback, (LPARAM) &childClipInfo);
2873 if (! contextClip.isEmpty())
2875 if (transparent)
2876 for (auto& i : contextClip)
2877 offscreenImage.clear (i);
2880 auto context = component.getLookAndFeel()
2881 .createGraphicsContext (offscreenImage, { -x, -y }, contextClip);
2883 context->addTransform (AffineTransform::scale ((float) getPlatformScaleFactor()));
2884 handlePaint (*context);
2887 static_cast<WindowsBitmapImage*> (offscreenImage.getPixelData())
2888 ->blitToWindow (hwnd, dc, transparent, x, y, updateLayeredWindowAlpha);
2891 if (childClipInfo.savedDC != 0)
2892 RestoreDC (dc, childClipInfo.savedDC);
2896 //==============================================================================
2897 void doMouseEvent (Point<float> position, float pressure, float orientation = 0.0f, ModifierKeys mods = ModifierKeys::currentModifiers)
2899 handleMouseEvent (MouseInputSource::InputSourceType::mouse, position, mods, pressure, orientation, getMouseEventTime());
2902 StringArray getAvailableRenderingEngines() override
2904 StringArray s ("Software Renderer");
2906 #if JUCE_DIRECT2D
2907 if (SystemStats::getOperatingSystemType() >= SystemStats::Windows7)
2908 s.add ("Direct2D");
2909 #endif
2911 return s;
2914 int getCurrentRenderingEngine() const override { return currentRenderingEngine; }
2916 #if JUCE_DIRECT2D
2917 void updateDirect2DContext()
2919 if (currentRenderingEngine != direct2DRenderingEngine)
2920 direct2DContext = nullptr;
2921 else if (direct2DContext == nullptr)
2922 direct2DContext.reset (new Direct2DLowLevelGraphicsContext (hwnd));
2924 #endif
2926 void setCurrentRenderingEngine (int index) override
2928 ignoreUnused (index);
2930 #if JUCE_DIRECT2D
2931 if (getAvailableRenderingEngines().size() > 1)
2933 currentRenderingEngine = index == 1 ? direct2DRenderingEngine : softwareRenderingEngine;
2934 updateDirect2DContext();
2935 repaint (component.getLocalBounds());
2937 #endif
2940 static uint32 getMinTimeBetweenMouseMoves()
2942 if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista)
2943 return 0;
2945 return 1000 / 60; // Throttling the incoming mouse-events seems to still be needed in XP..
2948 bool isTouchEvent() noexcept
2950 if (registerTouchWindow == nullptr)
2951 return false;
2953 // Relevant info about touch/pen detection flags:
2954 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms703320(v=vs.85).aspx
2955 // http://www.petertissen.de/?p=4
2957 return ((uint32_t) GetMessageExtraInfo() & 0xFFFFFF80 /*SIGNATURE_MASK*/) == 0xFF515780 /*MI_WP_SIGNATURE*/;
2960 static bool areOtherTouchSourcesActive()
2962 for (auto& ms : Desktop::getInstance().getMouseSources())
2963 if (ms.isDragging() && (ms.getType() == MouseInputSource::InputSourceType::touch
2964 || ms.getType() == MouseInputSource::InputSourceType::pen))
2965 return true;
2967 return false;
2970 void doMouseMove (Point<float> position, bool isMouseDownEvent)
2972 ModifierKeys modsToSend (ModifierKeys::currentModifiers);
2974 // this will be handled by WM_TOUCH
2975 if (isTouchEvent() || areOtherTouchSourcesActive())
2976 return;
2978 if (! isMouseOver)
2980 isMouseOver = true;
2982 // This avoids a rare stuck-button problem when focus is lost unexpectedly, but must
2983 // not be called as part of a move, in case it's actually a mouse-drag from another
2984 // app which ends up here when we get focus before the mouse is released..
2985 if (isMouseDownEvent && getNativeRealtimeModifiers != nullptr)
2986 getNativeRealtimeModifiers();
2988 updateKeyModifiers();
2990 #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client
2991 if (modProvider != nullptr)
2992 ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withFlags (modProvider->getWin32Modifiers());
2993 #endif
2995 TRACKMOUSEEVENT tme;
2996 tme.cbSize = sizeof (tme);
2997 tme.dwFlags = TME_LEAVE;
2998 tme.hwndTrack = hwnd;
2999 tme.dwHoverTime = 0;
3001 if (! TrackMouseEvent (&tme))
3002 jassertfalse;
3004 Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate();
3006 else if (! isDragging)
3008 if (! contains (position.roundToInt(), false))
3009 return;
3012 static uint32 lastMouseTime = 0;
3013 static auto minTimeBetweenMouses = getMinTimeBetweenMouseMoves();
3014 auto now = Time::getMillisecondCounter();
3016 if (! Desktop::getInstance().getMainMouseSource().isDragging())
3017 modsToSend = modsToSend.withoutMouseButtons();
3019 if (now >= lastMouseTime + minTimeBetweenMouses)
3021 lastMouseTime = now;
3022 doMouseEvent (position, MouseInputSource::defaultPressure,
3023 MouseInputSource::defaultOrientation, modsToSend);
3027 void doMouseDown (Point<float> position, const WPARAM wParam)
3029 // this will be handled by WM_TOUCH
3030 if (isTouchEvent() || areOtherTouchSourcesActive())
3031 return;
3033 if (GetCapture() != hwnd)
3034 SetCapture (hwnd);
3036 doMouseMove (position, true);
3038 if (isValidPeer (this))
3040 updateModifiersFromWParam (wParam);
3042 #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client
3043 if (modProvider != nullptr)
3044 ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withFlags (modProvider->getWin32Modifiers());
3045 #endif
3047 isDragging = true;
3049 doMouseEvent (position, MouseInputSource::defaultPressure);
3053 void doMouseUp (Point<float> position, const WPARAM wParam)
3055 // this will be handled by WM_TOUCH
3056 if (isTouchEvent() || areOtherTouchSourcesActive())
3057 return;
3059 updateModifiersFromWParam (wParam);
3061 #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client
3062 if (modProvider != nullptr)
3063 ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withFlags (modProvider->getWin32Modifiers());
3064 #endif
3066 const bool wasDragging = isDragging;
3067 isDragging = false;
3069 // release the mouse capture if the user has released all buttons
3070 if ((wParam & (MK_LBUTTON | MK_RBUTTON | MK_MBUTTON)) == 0 && hwnd == GetCapture())
3071 ReleaseCapture();
3073 // NB: under some circumstances (e.g. double-clicking a native title bar), a mouse-up can
3074 // arrive without a mouse-down, so in that case we need to avoid sending a message.
3075 if (wasDragging)
3076 doMouseEvent (position, MouseInputSource::defaultPressure);
3079 void doCaptureChanged()
3081 if (constrainerIsResizing)
3083 if (constrainer != nullptr)
3084 constrainer->resizeEnd();
3086 constrainerIsResizing = false;
3089 if (isDragging)
3090 doMouseUp (getCurrentMousePos(), (WPARAM) 0);
3093 void doMouseExit()
3095 isMouseOver = false;
3097 if (! areOtherTouchSourcesActive())
3098 doMouseEvent (getCurrentMousePos(), MouseInputSource::defaultPressure);
3101 ComponentPeer* findPeerUnderMouse (Point<float>& localPos)
3103 auto currentMousePos = getPOINTFromLParam ((LPARAM) GetMessagePos());
3105 // Because Windows stupidly sends all wheel events to the window with the keyboard
3106 // focus, we have to redirect them here according to the mouse pos..
3107 auto* peer = getOwnerOfWindow (WindowFromPoint (currentMousePos));
3109 if (peer == nullptr)
3110 peer = this;
3112 localPos = peer->globalToLocal (convertPhysicalScreenPointToLogical (pointFromPOINT (currentMousePos), hwnd).toFloat());
3113 return peer;
3116 static MouseInputSource::InputSourceType getPointerType (WPARAM wParam)
3118 if (getPointerTypeFunction != nullptr)
3120 POINTER_INPUT_TYPE pointerType;
3122 if (getPointerTypeFunction (GET_POINTERID_WPARAM (wParam), &pointerType))
3124 if (pointerType == 2)
3125 return MouseInputSource::InputSourceType::touch;
3127 if (pointerType == 3)
3128 return MouseInputSource::InputSourceType::pen;
3132 return MouseInputSource::InputSourceType::mouse;
3135 void doMouseWheel (const WPARAM wParam, const bool isVertical)
3137 updateKeyModifiers();
3138 const float amount = jlimit (-1000.0f, 1000.0f, 0.5f * (short) HIWORD (wParam));
3140 MouseWheelDetails wheel;
3141 wheel.deltaX = isVertical ? 0.0f : amount / -256.0f;
3142 wheel.deltaY = isVertical ? amount / 256.0f : 0.0f;
3143 wheel.isReversed = false;
3144 wheel.isSmooth = false;
3145 wheel.isInertial = false;
3147 Point<float> localPos;
3149 if (auto* peer = findPeerUnderMouse (localPos))
3150 peer->handleMouseWheel (getPointerType (wParam), localPos, getMouseEventTime(), wheel);
3153 bool doGestureEvent (LPARAM lParam)
3155 GESTUREINFO gi;
3156 zerostruct (gi);
3157 gi.cbSize = sizeof (gi);
3159 if (getGestureInfo != nullptr && getGestureInfo ((HGESTUREINFO) lParam, &gi))
3161 updateKeyModifiers();
3162 Point<float> localPos;
3164 if (auto* peer = findPeerUnderMouse (localPos))
3166 switch (gi.dwID)
3168 case 3: /*GID_ZOOM*/
3169 if (gi.dwFlags != 1 /*GF_BEGIN*/ && lastMagnifySize > 0)
3170 peer->handleMagnifyGesture (MouseInputSource::InputSourceType::touch, localPos, getMouseEventTime(),
3171 (float) ((double) gi.ullArguments / (double) lastMagnifySize));
3173 lastMagnifySize = gi.ullArguments;
3174 return true;
3176 case 4: /*GID_PAN*/
3177 case 5: /*GID_ROTATE*/
3178 case 6: /*GID_TWOFINGERTAP*/
3179 case 7: /*GID_PRESSANDTAP*/
3180 default:
3181 break;
3186 return false;
3189 LRESULT doTouchEvent (const int numInputs, HTOUCHINPUT eventHandle)
3191 if ((styleFlags & windowIgnoresMouseClicks) != 0)
3192 if (auto* parent = getOwnerOfWindow (GetParent (hwnd)))
3193 if (parent != this)
3194 return parent->doTouchEvent (numInputs, eventHandle);
3196 HeapBlock<TOUCHINPUT> inputInfo (numInputs);
3198 if (getTouchInputInfo (eventHandle, (UINT) numInputs, inputInfo, sizeof (TOUCHINPUT)))
3200 for (int i = 0; i < numInputs; ++i)
3202 auto flags = inputInfo[i].dwFlags;
3204 if ((flags & (TOUCHEVENTF_DOWN | TOUCHEVENTF_MOVE | TOUCHEVENTF_UP)) != 0)
3205 if (! handleTouchInput (inputInfo[i], (flags & TOUCHEVENTF_DOWN) != 0, (flags & TOUCHEVENTF_UP) != 0))
3206 return 0; // abandon method if this window was deleted by the callback
3210 closeTouchInputHandle (eventHandle);
3211 return 0;
3214 bool handleTouchInput (const TOUCHINPUT& touch, const bool isDown, const bool isUp,
3215 const float touchPressure = MouseInputSource::defaultPressure,
3216 const float orientation = 0.0f)
3218 auto isCancel = false;
3220 const auto touchIndex = currentTouches.getIndexOfTouch (this, touch.dwID);
3221 const auto time = getMouseEventTime();
3222 const auto pos = globalToLocal (convertPhysicalScreenPointToLogical (pointFromPOINT ({ roundToInt (touch.x / 100.0f),
3223 roundToInt (touch.y / 100.0f) }), hwnd).toFloat());
3224 const auto pressure = touchPressure;
3225 auto modsToSend = ModifierKeys::currentModifiers;
3227 if (isDown)
3229 ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier);
3230 modsToSend = ModifierKeys::currentModifiers;
3232 // this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before.
3233 handleMouseEvent (MouseInputSource::InputSourceType::touch, pos, modsToSend.withoutMouseButtons(),
3234 pressure, orientation, time, {}, touchIndex);
3236 if (! isValidPeer (this)) // (in case this component was deleted by the event)
3237 return false;
3239 else if (isUp)
3241 modsToSend = modsToSend.withoutMouseButtons();
3242 ModifierKeys::currentModifiers = modsToSend;
3243 currentTouches.clearTouch (touchIndex);
3245 if (! currentTouches.areAnyTouchesActive())
3246 isCancel = true;
3248 else
3250 modsToSend = ModifierKeys::currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier);
3253 handleMouseEvent (MouseInputSource::InputSourceType::touch, pos, modsToSend,
3254 pressure, orientation, time, {}, touchIndex);
3256 if (! isValidPeer (this))
3257 return false;
3259 if (isUp)
3261 handleMouseEvent (MouseInputSource::InputSourceType::touch, MouseInputSource::offscreenMousePos, ModifierKeys::currentModifiers.withoutMouseButtons(),
3262 pressure, orientation, time, {}, touchIndex);
3264 if (! isValidPeer (this))
3265 return false;
3267 if (isCancel)
3269 currentTouches.clear();
3270 ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons();
3274 return true;
3277 bool handlePointerInput (WPARAM wParam, LPARAM lParam, const bool isDown, const bool isUp)
3279 if (! canUsePointerAPI)
3280 return false;
3282 auto pointerType = getPointerType (wParam);
3284 if (pointerType == MouseInputSource::InputSourceType::touch)
3286 POINTER_TOUCH_INFO touchInfo;
3288 if (! getPointerTouchInfo (GET_POINTERID_WPARAM (wParam), &touchInfo))
3289 return false;
3291 const auto pressure = touchInfo.touchMask & TOUCH_MASK_PRESSURE ? static_cast<float> (touchInfo.pressure)
3292 : MouseInputSource::defaultPressure;
3293 const auto orientation = touchInfo.touchMask & TOUCH_MASK_ORIENTATION ? degreesToRadians (static_cast<float> (touchInfo.orientation))
3294 : MouseInputSource::defaultOrientation;
3296 if (! handleTouchInput (emulateTouchEventFromPointer (touchInfo.pointerInfo.ptPixelLocationRaw, wParam),
3297 isDown, isUp, pressure, orientation))
3298 return false;
3300 else if (pointerType == MouseInputSource::InputSourceType::pen)
3302 POINTER_PEN_INFO penInfo;
3304 if (! getPointerPenInfo (GET_POINTERID_WPARAM (wParam), &penInfo))
3305 return false;
3307 const auto pressure = (penInfo.penMask & PEN_MASK_PRESSURE) ? (float) penInfo.pressure / 1024.0f : MouseInputSource::defaultPressure;
3309 if (! handlePenInput (penInfo, globalToLocal (convertPhysicalScreenPointToLogical (pointFromPOINT (getPOINTFromLParam (lParam)), hwnd).toFloat()),
3310 pressure, isDown, isUp))
3311 return false;
3313 else
3315 return false;
3318 return true;
3321 TOUCHINPUT emulateTouchEventFromPointer (POINT p, WPARAM wParam)
3323 TOUCHINPUT touchInput;
3325 touchInput.dwID = GET_POINTERID_WPARAM (wParam);
3326 touchInput.x = p.x * 100;
3327 touchInput.y = p.y * 100;
3329 return touchInput;
3332 bool handlePenInput (POINTER_PEN_INFO penInfo, Point<float> pos, const float pressure, bool isDown, bool isUp)
3334 const auto time = getMouseEventTime();
3335 ModifierKeys modsToSend (ModifierKeys::currentModifiers);
3336 PenDetails penDetails;
3338 penDetails.rotation = (penInfo.penMask & PEN_MASK_ROTATION) ? degreesToRadians (static_cast<float> (penInfo.rotation)) : MouseInputSource::defaultRotation;
3339 penDetails.tiltX = (penInfo.penMask & PEN_MASK_TILT_X) ? (float) penInfo.tiltX / 90.0f : MouseInputSource::defaultTiltX;
3340 penDetails.tiltY = (penInfo.penMask & PEN_MASK_TILT_Y) ? (float) penInfo.tiltY / 90.0f : MouseInputSource::defaultTiltY;
3342 auto pInfoFlags = penInfo.pointerInfo.pointerFlags;
3344 if ((pInfoFlags & POINTER_FLAG_FIRSTBUTTON) != 0)
3345 ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier);
3346 else if ((pInfoFlags & POINTER_FLAG_SECONDBUTTON) != 0)
3347 ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::rightButtonModifier);
3349 if (isDown)
3351 modsToSend = ModifierKeys::currentModifiers;
3353 // this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before.
3354 handleMouseEvent (MouseInputSource::InputSourceType::pen, pos, modsToSend.withoutMouseButtons(),
3355 pressure, MouseInputSource::defaultOrientation, time, penDetails);
3357 if (! isValidPeer (this)) // (in case this component was deleted by the event)
3358 return false;
3360 else if (isUp || ! (pInfoFlags & POINTER_FLAG_INCONTACT))
3362 modsToSend = modsToSend.withoutMouseButtons();
3363 ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons();
3366 handleMouseEvent (MouseInputSource::InputSourceType::pen, pos, modsToSend, pressure,
3367 MouseInputSource::defaultOrientation, time, penDetails);
3369 if (! isValidPeer (this)) // (in case this component was deleted by the event)
3370 return false;
3372 if (isUp)
3374 handleMouseEvent (MouseInputSource::InputSourceType::pen, MouseInputSource::offscreenMousePos, ModifierKeys::currentModifiers,
3375 pressure, MouseInputSource::defaultOrientation, time, penDetails);
3377 if (! isValidPeer (this))
3378 return false;
3381 return true;
3384 //==============================================================================
3385 void sendModifierKeyChangeIfNeeded()
3387 if (modifiersAtLastCallback != ModifierKeys::currentModifiers)
3389 modifiersAtLastCallback = ModifierKeys::currentModifiers;
3390 handleModifierKeysChange();
3394 bool doKeyUp (const WPARAM key)
3396 updateKeyModifiers();
3398 switch (key)
3400 case VK_SHIFT:
3401 case VK_CONTROL:
3402 case VK_MENU:
3403 case VK_CAPITAL:
3404 case VK_LWIN:
3405 case VK_RWIN:
3406 case VK_APPS:
3407 case VK_NUMLOCK:
3408 case VK_SCROLL:
3409 case VK_LSHIFT:
3410 case VK_RSHIFT:
3411 case VK_LCONTROL:
3412 case VK_LMENU:
3413 case VK_RCONTROL:
3414 case VK_RMENU:
3415 sendModifierKeyChangeIfNeeded();
3418 return handleKeyUpOrDown (false)
3419 || Component::getCurrentlyModalComponent() != nullptr;
3422 bool doKeyDown (const WPARAM key)
3424 updateKeyModifiers();
3425 bool used = false;
3427 switch (key)
3429 case VK_SHIFT:
3430 case VK_LSHIFT:
3431 case VK_RSHIFT:
3432 case VK_CONTROL:
3433 case VK_LCONTROL:
3434 case VK_RCONTROL:
3435 case VK_MENU:
3436 case VK_LMENU:
3437 case VK_RMENU:
3438 case VK_LWIN:
3439 case VK_RWIN:
3440 case VK_CAPITAL:
3441 case VK_NUMLOCK:
3442 case VK_SCROLL:
3443 case VK_APPS:
3444 used = handleKeyUpOrDown (true);
3445 sendModifierKeyChangeIfNeeded();
3446 break;
3448 case VK_LEFT:
3449 case VK_RIGHT:
3450 case VK_UP:
3451 case VK_DOWN:
3452 case VK_PRIOR:
3453 case VK_NEXT:
3454 case VK_HOME:
3455 case VK_END:
3456 case VK_DELETE:
3457 case VK_INSERT:
3458 case VK_F1:
3459 case VK_F2:
3460 case VK_F3:
3461 case VK_F4:
3462 case VK_F5:
3463 case VK_F6:
3464 case VK_F7:
3465 case VK_F8:
3466 case VK_F9:
3467 case VK_F10:
3468 case VK_F11:
3469 case VK_F12:
3470 case VK_F13:
3471 case VK_F14:
3472 case VK_F15:
3473 case VK_F16:
3474 case VK_F17:
3475 case VK_F18:
3476 case VK_F19:
3477 case VK_F20:
3478 case VK_F21:
3479 case VK_F22:
3480 case VK_F23:
3481 case VK_F24:
3482 used = handleKeyUpOrDown (true);
3483 used = handleKeyPress (extendedKeyModifier | (int) key, 0) || used;
3484 break;
3486 default:
3487 used = handleKeyUpOrDown (true);
3490 MSG msg;
3491 if (! PeekMessage (&msg, hwnd, WM_CHAR, WM_DEADCHAR, PM_NOREMOVE))
3493 // if there isn't a WM_CHAR or WM_DEADCHAR message pending, we need to
3494 // manually generate the key-press event that matches this key-down.
3495 const UINT keyChar = MapVirtualKey ((UINT) key, 2);
3496 const UINT scanCode = MapVirtualKey ((UINT) key, 0);
3497 BYTE keyState[256];
3498 ignoreUnused (GetKeyboardState (keyState));
3500 WCHAR text[16] = { 0 };
3501 if (ToUnicode ((UINT) key, scanCode, keyState, text, 8, 0) != 1)
3502 text[0] = 0;
3504 used = handleKeyPress ((int) LOWORD (keyChar), (juce_wchar) text[0]) || used;
3508 break;
3511 return used || (Component::getCurrentlyModalComponent() != nullptr);
3514 bool doKeyChar (int key, const LPARAM flags)
3516 updateKeyModifiers();
3518 auto textChar = (juce_wchar) key;
3519 const int virtualScanCode = (flags >> 16) & 0xff;
3521 if (key >= '0' && key <= '9')
3523 switch (virtualScanCode) // check for a numeric keypad scan-code
3525 case 0x52:
3526 case 0x4f:
3527 case 0x50:
3528 case 0x51:
3529 case 0x4b:
3530 case 0x4c:
3531 case 0x4d:
3532 case 0x47:
3533 case 0x48:
3534 case 0x49:
3535 key = (key - '0') + KeyPress::numberPad0;
3536 break;
3537 default:
3538 break;
3541 else
3543 // convert the scan code to an unmodified character code..
3544 const UINT virtualKey = MapVirtualKey ((UINT) virtualScanCode, 1);
3545 UINT keyChar = MapVirtualKey (virtualKey, 2);
3547 keyChar = LOWORD (keyChar);
3549 if (keyChar != 0)
3550 key = (int) keyChar;
3552 // avoid sending junk text characters for some control-key combinations
3553 if (textChar < ' ' && ModifierKeys::currentModifiers.testFlags (ModifierKeys::ctrlModifier | ModifierKeys::altModifier))
3554 textChar = 0;
3557 return handleKeyPress (key, textChar);
3560 void forwardMessageToParent (UINT message, WPARAM wParam, LPARAM lParam) const
3562 if (HWND parentH = GetParent (hwnd))
3563 PostMessage (parentH, message, wParam, lParam);
3566 bool doAppCommand (const LPARAM lParam)
3568 int key = 0;
3570 switch (GET_APPCOMMAND_LPARAM (lParam))
3572 case APPCOMMAND_MEDIA_PLAY_PAUSE: key = KeyPress::playKey; break;
3573 case APPCOMMAND_MEDIA_STOP: key = KeyPress::stopKey; break;
3574 case APPCOMMAND_MEDIA_NEXTTRACK: key = KeyPress::fastForwardKey; break;
3575 case APPCOMMAND_MEDIA_PREVIOUSTRACK: key = KeyPress::rewindKey; break;
3576 default: break;
3579 if (key != 0)
3581 updateKeyModifiers();
3583 if (hwnd == GetActiveWindow())
3584 return handleKeyPress (key, 0);
3587 return false;
3590 bool isConstrainedNativeWindow() const
3592 return constrainer != nullptr
3593 && (styleFlags & (windowHasTitleBar | windowIsResizable)) == (windowHasTitleBar | windowIsResizable)
3594 && ! isKioskMode();
3597 Rectangle<int> getCurrentScaledBounds() const
3599 return ScalingHelpers::unscaledScreenPosToScaled (component, windowBorder.addedTo (ScalingHelpers::scaledScreenPosToUnscaled (component, component.getBounds())));
3602 LRESULT handleSizeConstraining (RECT& r, const WPARAM wParam)
3604 if (isConstrainedNativeWindow())
3606 const auto logicalBounds = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (r).toFloat(), hwnd);
3607 auto pos = ScalingHelpers::unscaledScreenPosToScaled (component, logicalBounds).toNearestInt();
3609 const auto original = getCurrentScaledBounds();
3611 constrainer->checkBounds (pos, original,
3612 Desktop::getInstance().getDisplays().getTotalBounds (true),
3613 wParam == WMSZ_TOP || wParam == WMSZ_TOPLEFT || wParam == WMSZ_TOPRIGHT,
3614 wParam == WMSZ_LEFT || wParam == WMSZ_TOPLEFT || wParam == WMSZ_BOTTOMLEFT,
3615 wParam == WMSZ_BOTTOM || wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_BOTTOMRIGHT,
3616 wParam == WMSZ_RIGHT || wParam == WMSZ_TOPRIGHT || wParam == WMSZ_BOTTOMRIGHT);
3618 r = RECTFromRectangle (convertLogicalScreenRectangleToPhysical (ScalingHelpers::scaledScreenPosToUnscaled (component, pos.toFloat()).toNearestInt(), hwnd));
3621 return TRUE;
3624 LRESULT handlePositionChanging (WINDOWPOS& wp)
3626 if (isConstrainedNativeWindow() && ! isFullScreen())
3628 if ((wp.flags & (SWP_NOMOVE | SWP_NOSIZE)) != (SWP_NOMOVE | SWP_NOSIZE)
3629 && (wp.x > -32000 && wp.y > -32000)
3630 && ! Component::isMouseButtonDownAnywhere())
3632 const auto logicalBounds = convertPhysicalScreenRectangleToLogical (rectangleFromRECT ({ wp.x, wp.y, wp.x + wp.cx, wp.y + wp.cy }).toFloat(), hwnd);
3633 auto pos = ScalingHelpers::unscaledScreenPosToScaled (component, logicalBounds).toNearestInt();
3635 const auto original = getCurrentScaledBounds();
3637 constrainer->checkBounds (pos, original,
3638 Desktop::getInstance().getDisplays().getTotalBounds (true),
3639 pos.getY() != original.getY() && pos.getBottom() == original.getBottom(),
3640 pos.getX() != original.getX() && pos.getRight() == original.getRight(),
3641 pos.getY() == original.getY() && pos.getBottom() != original.getBottom(),
3642 pos.getX() == original.getX() && pos.getRight() != original.getRight());
3644 auto physicalBounds = convertLogicalScreenRectangleToPhysical (ScalingHelpers::scaledScreenPosToUnscaled (component, pos.toFloat()), hwnd);
3646 auto getNewPositionIfNotRoundingError = [] (int posIn, float newPos)
3648 return (std::abs ((float) posIn - newPos) >= 1.0f) ? roundToInt (newPos) : posIn;
3651 wp.x = getNewPositionIfNotRoundingError (wp.x, physicalBounds.getX());
3652 wp.y = getNewPositionIfNotRoundingError (wp.y, physicalBounds.getY());
3653 wp.cx = getNewPositionIfNotRoundingError (wp.cx, physicalBounds.getWidth());
3654 wp.cy = getNewPositionIfNotRoundingError (wp.cy, physicalBounds.getHeight());
3658 if (((wp.flags & SWP_SHOWWINDOW) != 0 && ! component.isVisible()))
3659 component.setVisible (true);
3660 else if (((wp.flags & SWP_HIDEWINDOW) != 0 && component.isVisible()))
3661 component.setVisible (false);
3663 return 0;
3666 bool updateCurrentMonitor()
3668 auto monitor = MonitorFromWindow (hwnd, MONITOR_DEFAULTTONULL);
3669 return std::exchange (currentMonitor, monitor) != monitor;
3672 bool handlePositionChanged()
3674 auto pos = getCurrentMousePos();
3676 if (contains (pos.roundToInt(), false))
3678 const ScopedValueSetter<bool> scope (inHandlePositionChanged, true);
3680 if (! areOtherTouchSourcesActive())
3681 doMouseEvent (pos, MouseInputSource::defaultPressure);
3683 if (! isValidPeer (this))
3684 return true;
3687 handleMovedOrResized();
3689 #if JUCE_MSVC
3690 if (updateCurrentMonitor())
3691 VBlankDispatcher::getInstance()->updateDisplay (*this, currentMonitor);
3692 #else
3693 updateCurrentMonitor();
3694 #endif
3696 return ! dontRepaint; // to allow non-accelerated openGL windows to draw themselves correctly.
3699 //==============================================================================
3700 LRESULT handleDPIChanging (int newDPI, RECT newRect)
3702 // Sometimes, windows that should not be automatically scaled (secondary windows in plugins)
3703 // are sent WM_DPICHANGED. The size suggested by the OS is incorrect for our unscaled
3704 // window, so we should ignore it.
3705 if (! isPerMonitorDPIAwareWindow (hwnd))
3706 return 0;
3708 const auto newScale = (double) newDPI / USER_DEFAULT_SCREEN_DPI;
3710 if (approximatelyEqual (scaleFactor, newScale))
3711 return 0;
3713 scaleFactor = newScale;
3716 const ScopedValueSetter<bool> setter (inDpiChange, true);
3717 SetWindowPos (hwnd,
3718 nullptr,
3719 newRect.left,
3720 newRect.top,
3721 newRect.right - newRect.left,
3722 newRect.bottom - newRect.top,
3723 SWP_NOZORDER | SWP_NOACTIVATE);
3726 // This is to handle reentrancy. If responding to a DPI change triggers further DPI changes,
3727 // we should only notify listeners and resize windows once all of the DPI changes have
3728 // resolved.
3729 if (inDpiChange)
3731 // Danger! Re-entrant call to handleDPIChanging.
3732 // Please report this issue on the JUCE forum, along with instructions
3733 // so that a JUCE developer can reproduce the issue.
3734 jassertfalse;
3735 return 0;
3738 updateShadower();
3739 InvalidateRect (hwnd, nullptr, FALSE);
3741 scaleFactorListeners.call ([this] (ScaleFactorListener& l) { l.nativeScaleFactorChanged (scaleFactor); });
3743 return 0;
3746 //==============================================================================
3747 void handleAppActivation (const WPARAM wParam)
3749 modifiersAtLastCallback = -1;
3750 updateKeyModifiers();
3752 if (isMinimised())
3754 component.repaint();
3755 handleMovedOrResized();
3757 if (! isValidPeer (this))
3758 return;
3761 auto* underMouse = component.getComponentAt (component.getMouseXYRelative());
3763 if (underMouse == nullptr)
3764 underMouse = &component;
3766 if (underMouse->isCurrentlyBlockedByAnotherModalComponent())
3768 if (LOWORD (wParam) == WA_CLICKACTIVE)
3769 Component::getCurrentlyModalComponent()->inputAttemptWhenModal();
3770 else
3771 ModalComponentManager::getInstance()->bringModalComponentsToFront();
3773 else
3775 handleBroughtToFront();
3779 void handlePowerBroadcast (WPARAM wParam)
3781 if (auto* app = JUCEApplicationBase::getInstance())
3783 switch (wParam)
3785 case PBT_APMSUSPEND: app->suspended(); break;
3787 case PBT_APMQUERYSUSPENDFAILED:
3788 case PBT_APMRESUMECRITICAL:
3789 case PBT_APMRESUMESUSPEND:
3790 case PBT_APMRESUMEAUTOMATIC: app->resumed(); break;
3792 default: break;
3797 void handleLeftClickInNCArea (WPARAM wParam)
3799 if (! sendInputAttemptWhenModalMessage())
3801 switch (wParam)
3803 case HTBOTTOM:
3804 case HTBOTTOMLEFT:
3805 case HTBOTTOMRIGHT:
3806 case HTGROWBOX:
3807 case HTLEFT:
3808 case HTRIGHT:
3809 case HTTOP:
3810 case HTTOPLEFT:
3811 case HTTOPRIGHT:
3812 if (isConstrainedNativeWindow())
3814 constrainerIsResizing = true;
3815 constrainer->resizeStart();
3817 break;
3819 default:
3820 break;
3825 void initialiseSysMenu (HMENU menu) const
3827 if (! hasTitleBar())
3829 if (isFullScreen())
3831 EnableMenuItem (menu, SC_RESTORE, MF_BYCOMMAND | MF_ENABLED);
3832 EnableMenuItem (menu, SC_MOVE, MF_BYCOMMAND | MF_GRAYED);
3834 else if (! isMinimised())
3836 EnableMenuItem (menu, SC_MAXIMIZE, MF_BYCOMMAND | MF_GRAYED);
3841 void doSettingChange()
3843 forceDisplayUpdate();
3845 if (fullScreen && ! isMinimised())
3846 setWindowPos (hwnd, ScalingHelpers::scaledScreenPosToUnscaled (component, Desktop::getInstance().getDisplays()
3847 .getDisplayForRect (component.getScreenBounds())->userArea),
3848 SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOSENDCHANGING);
3850 #if JUCE_MSVC
3851 auto* dispatcher = VBlankDispatcher::getInstance();
3852 dispatcher->reconfigureDisplays();
3853 updateCurrentMonitor();
3854 dispatcher->updateDisplay (*this, currentMonitor);
3855 #else
3856 updateCurrentMonitor();
3857 #endif
3860 //==============================================================================
3861 #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client
3862 void setModifierKeyProvider (ModifierKeyProvider* provider) override
3864 modProvider = provider;
3867 void removeModifierKeyProvider() override
3869 modProvider = nullptr;
3871 #endif
3873 public:
3874 static LRESULT CALLBACK windowProc (HWND h, UINT message, WPARAM wParam, LPARAM lParam)
3876 // Ensure that non-client areas are scaled for per-monitor DPI awareness v1 - can't
3877 // do this in peerWindowProc as we have no window at this point
3878 if (message == WM_NCCREATE && enableNonClientDPIScaling != nullptr)
3879 enableNonClientDPIScaling (h);
3881 if (auto* peer = getOwnerOfWindow (h))
3883 jassert (isValidPeer (peer));
3884 return peer->peerWindowProc (h, message, wParam, lParam);
3887 return DefWindowProcW (h, message, wParam, lParam);
3890 private:
3891 static void* callFunctionIfNotLocked (MessageCallbackFunction* callback, void* userData)
3893 auto& mm = *MessageManager::getInstance();
3895 if (mm.currentThreadHasLockedMessageManager())
3896 return callback (userData);
3898 return mm.callFunctionOnMessageThread (callback, userData);
3901 static POINT getPOINTFromLParam (LPARAM lParam) noexcept
3903 return { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) };
3906 Point<float> getPointFromLocalLParam (LPARAM lParam) noexcept
3908 auto p = pointFromPOINT (getPOINTFromLParam (lParam));
3910 if (isPerMonitorDPIAwareWindow (hwnd))
3912 // LPARAM is relative to this window's top-left but may be on a different monitor so we need to calculate the
3913 // physical screen position and then convert this to local logical coordinates
3914 auto r = getWindowScreenRect (hwnd);
3915 return globalToLocal (Desktop::getInstance().getDisplays().physicalToLogical (pointFromPOINT ({ r.left + p.x + roundToInt (windowBorder.getLeft() * scaleFactor),
3916 r.top + p.y + roundToInt (windowBorder.getTop() * scaleFactor) })).toFloat());
3919 return p.toFloat();
3922 Point<float> getCurrentMousePos() noexcept
3924 return globalToLocal (convertPhysicalScreenPointToLogical (pointFromPOINT (getPOINTFromLParam ((LPARAM) GetMessagePos())), hwnd).toFloat());
3927 LRESULT peerWindowProc (HWND h, UINT message, WPARAM wParam, LPARAM lParam)
3929 switch (message)
3931 //==============================================================================
3932 case WM_NCHITTEST:
3933 if ((styleFlags & windowIgnoresMouseClicks) != 0)
3934 return HTTRANSPARENT;
3936 if (! hasTitleBar())
3937 return HTCLIENT;
3939 break;
3941 //==============================================================================
3942 case WM_PAINT:
3943 handlePaintMessage();
3944 return 0;
3946 case WM_NCPAINT:
3947 handlePaintMessage(); // this must be done, even with native titlebars, or there are rendering artifacts.
3949 if (hasTitleBar())
3950 break; // let the DefWindowProc handle drawing the frame.
3952 return 0;
3954 case WM_ERASEBKGND:
3955 case WM_NCCALCSIZE:
3956 if (hasTitleBar())
3957 break;
3959 return 1;
3961 //==============================================================================
3962 case WM_POINTERUPDATE:
3963 if (handlePointerInput (wParam, lParam, false, false))
3964 return 0;
3965 break;
3967 case WM_POINTERDOWN:
3968 if (handlePointerInput (wParam, lParam, true, false))
3969 return 0;
3970 break;
3972 case WM_POINTERUP:
3973 if (handlePointerInput (wParam, lParam, false, true))
3974 return 0;
3975 break;
3977 //==============================================================================
3978 case WM_MOUSEMOVE: doMouseMove (getPointFromLocalLParam (lParam), false); return 0;
3980 case WM_POINTERLEAVE:
3981 case WM_MOUSELEAVE: doMouseExit(); return 0;
3983 case WM_LBUTTONDOWN:
3984 case WM_MBUTTONDOWN:
3985 case WM_RBUTTONDOWN: doMouseDown (getPointFromLocalLParam (lParam), wParam); return 0;
3987 case WM_LBUTTONUP:
3988 case WM_MBUTTONUP:
3989 case WM_RBUTTONUP: doMouseUp (getPointFromLocalLParam (lParam), wParam); return 0;
3991 case WM_POINTERWHEEL:
3992 case 0x020A: /* WM_MOUSEWHEEL */ doMouseWheel (wParam, true); return 0;
3994 case WM_POINTERHWHEEL:
3995 case 0x020E: /* WM_MOUSEHWHEEL */ doMouseWheel (wParam, false); return 0;
3997 case WM_CAPTURECHANGED: doCaptureChanged(); return 0;
3999 case WM_NCPOINTERUPDATE:
4000 case WM_NCMOUSEMOVE:
4001 if (hasTitleBar())
4002 break;
4004 return 0;
4006 case WM_TOUCH:
4007 if (getTouchInputInfo != nullptr)
4008 return doTouchEvent ((int) wParam, (HTOUCHINPUT) lParam);
4010 break;
4012 case 0x119: /* WM_GESTURE */
4013 if (doGestureEvent (lParam))
4014 return 0;
4016 break;
4018 //==============================================================================
4019 case WM_SIZING: return handleSizeConstraining (*(RECT*) lParam, wParam);
4020 case WM_WINDOWPOSCHANGING: return handlePositionChanging (*(WINDOWPOS*) lParam);
4021 case 0x2e0: /* WM_DPICHANGED */ return handleDPIChanging ((int) HIWORD (wParam), *(RECT*) lParam);
4023 case WM_WINDOWPOSCHANGED:
4025 const WINDOWPOS& wPos = *reinterpret_cast<WINDOWPOS*> (lParam);
4027 if ((wPos.flags & SWP_NOMOVE) != 0 && (wPos.flags & SWP_NOSIZE) != 0)
4028 startTimer (100);
4029 else
4030 if (handlePositionChanged())
4031 return 0;
4033 break;
4035 //==============================================================================
4036 case WM_KEYDOWN:
4037 case WM_SYSKEYDOWN:
4038 if (doKeyDown (wParam))
4039 return 0;
4041 forwardMessageToParent (message, wParam, lParam);
4042 break;
4044 case WM_KEYUP:
4045 case WM_SYSKEYUP:
4046 if (doKeyUp (wParam))
4047 return 0;
4049 forwardMessageToParent (message, wParam, lParam);
4050 break;
4052 case WM_CHAR:
4053 if (doKeyChar ((int) wParam, lParam))
4054 return 0;
4056 forwardMessageToParent (message, wParam, lParam);
4057 break;
4059 case WM_APPCOMMAND:
4060 if (doAppCommand (lParam))
4061 return TRUE;
4063 break;
4065 case WM_MENUCHAR: // triggered when alt+something is pressed
4066 return MNC_CLOSE << 16; // (avoids making the default system beep)
4068 //==============================================================================
4069 case WM_SETFOCUS:
4070 updateKeyModifiers();
4071 handleFocusGain();
4072 break;
4074 case WM_KILLFOCUS:
4075 if (hasCreatedCaret)
4077 hasCreatedCaret = false;
4078 DestroyCaret();
4081 handleFocusLoss();
4083 if (auto* modal = Component::getCurrentlyModalComponent())
4084 if (auto* peer = modal->getPeer())
4085 if ((peer->getStyleFlags() & ComponentPeer::windowIsTemporary) != 0)
4086 sendInputAttemptWhenModalMessage();
4088 break;
4090 case WM_ACTIVATEAPP:
4091 // Windows does weird things to process priority when you swap apps,
4092 // so this forces an update when the app is brought to the front
4093 if (wParam != FALSE)
4094 juce_repeatLastProcessPriority();
4095 else
4096 Desktop::getInstance().setKioskModeComponent (nullptr); // turn kiosk mode off if we lose focus
4098 juce_checkCurrentlyFocusedTopLevelWindow();
4099 modifiersAtLastCallback = -1;
4100 return 0;
4102 case WM_ACTIVATE:
4103 if (LOWORD (wParam) == WA_ACTIVE || LOWORD (wParam) == WA_CLICKACTIVE)
4105 handleAppActivation (wParam);
4106 return 0;
4109 break;
4111 case WM_NCACTIVATE:
4112 // while a temporary window is being shown, prevent Windows from deactivating the
4113 // title bars of our main windows.
4114 if (wParam == 0 && ! shouldDeactivateTitleBar)
4115 wParam = TRUE; // change this and let it get passed to the DefWindowProc.
4117 break;
4119 case WM_POINTERACTIVATE:
4120 case WM_MOUSEACTIVATE:
4121 if (! component.getMouseClickGrabsKeyboardFocus())
4122 return MA_NOACTIVATE;
4124 break;
4126 case WM_SHOWWINDOW:
4127 if (wParam != 0)
4129 component.setVisible (true);
4130 handleBroughtToFront();
4133 break;
4135 case WM_CLOSE:
4136 if (! component.isCurrentlyBlockedByAnotherModalComponent())
4137 handleUserClosingWindow();
4139 return 0;
4141 #if JUCE_REMOVE_COMPONENT_FROM_DESKTOP_ON_WM_DESTROY
4142 case WM_DESTROY:
4143 getComponent().removeFromDesktop();
4144 return 0;
4145 #endif
4147 case WM_QUERYENDSESSION:
4148 if (auto* app = JUCEApplicationBase::getInstance())
4150 app->systemRequestedQuit();
4151 return MessageManager::getInstance()->hasStopMessageBeenSent();
4153 return TRUE;
4155 case WM_POWERBROADCAST:
4156 handlePowerBroadcast (wParam);
4157 break;
4159 case WM_SYNCPAINT:
4160 return 0;
4162 case WM_DISPLAYCHANGE:
4163 InvalidateRect (h, nullptr, 0);
4164 // intentional fall-through...
4165 JUCE_FALLTHROUGH
4166 case WM_SETTINGCHANGE: // note the fall-through in the previous case!
4167 doSettingChange();
4168 break;
4170 case WM_INITMENU:
4171 initialiseSysMenu ((HMENU) wParam);
4172 break;
4174 case WM_SYSCOMMAND:
4175 switch (wParam & 0xfff0)
4177 case SC_CLOSE:
4178 if (sendInputAttemptWhenModalMessage())
4179 return 0;
4181 if (hasTitleBar())
4183 PostMessage (h, WM_CLOSE, 0, 0);
4184 return 0;
4186 break;
4188 case SC_KEYMENU:
4189 #if ! JUCE_WINDOWS_ALT_KEY_TRIGGERS_MENU
4190 // This test prevents a press of the ALT key from triggering the ancient top-left window menu.
4191 // By default we suppress this behaviour because it's unlikely that more than a tiny subset of
4192 // our users will actually want it, and it causes problems if you're trying to use the ALT key
4193 // as a modifier for mouse actions. If you really need the old behaviour, then just define
4194 // JUCE_WINDOWS_ALT_KEY_TRIGGERS_MENU=1 in your app.
4195 if ((lParam >> 16) <= 0) // Values above zero indicate that a mouse-click triggered the menu
4196 return 0;
4197 #endif
4199 // (NB mustn't call sendInputAttemptWhenModalMessage() here because of very obscure
4200 // situations that can arise if a modal loop is started from an alt-key keypress).
4201 if (hasTitleBar() && h == GetCapture())
4202 ReleaseCapture();
4204 break;
4206 case SC_MAXIMIZE:
4207 if (! sendInputAttemptWhenModalMessage())
4208 setFullScreen (true);
4210 return 0;
4212 case SC_MINIMIZE:
4213 if (sendInputAttemptWhenModalMessage())
4214 return 0;
4216 if (! hasTitleBar())
4218 setMinimised (true);
4219 return 0;
4221 break;
4223 case SC_RESTORE:
4224 if (sendInputAttemptWhenModalMessage())
4225 return 0;
4227 if (hasTitleBar())
4229 if (isFullScreen())
4231 setFullScreen (false);
4232 return 0;
4235 else
4237 if (isMinimised())
4238 setMinimised (false);
4239 else if (isFullScreen())
4240 setFullScreen (false);
4242 return 0;
4244 break;
4247 break;
4249 case WM_NCPOINTERDOWN:
4250 case WM_NCLBUTTONDOWN:
4251 handleLeftClickInNCArea (wParam);
4252 break;
4254 case WM_NCRBUTTONDOWN:
4255 case WM_NCMBUTTONDOWN:
4256 sendInputAttemptWhenModalMessage();
4257 break;
4259 case WM_IME_SETCONTEXT:
4260 imeHandler.handleSetContext (h, wParam == TRUE);
4261 lParam &= ~(LPARAM) ISC_SHOWUICOMPOSITIONWINDOW;
4262 break;
4264 case WM_IME_STARTCOMPOSITION: imeHandler.handleStartComposition (*this); return 0;
4265 case WM_IME_ENDCOMPOSITION: imeHandler.handleEndComposition (*this, h); break;
4266 case WM_IME_COMPOSITION: imeHandler.handleComposition (*this, h, lParam); return 0;
4268 case WM_GETDLGCODE:
4269 return DLGC_WANTALLKEYS;
4271 #if JUCE_MSVC
4272 case WM_GETOBJECT:
4274 if (static_cast<long> (lParam) == WindowsAccessibility::getUiaRootObjectId())
4276 if (auto* handler = component.getAccessibilityHandler())
4278 LRESULT res = 0;
4280 if (WindowsAccessibility::handleWmGetObject (handler, wParam, lParam, &res))
4282 isAccessibilityActive = true;
4283 return res;
4288 break;
4290 #endif
4291 default:
4292 break;
4295 return DefWindowProcW (h, message, wParam, lParam);
4298 bool sendInputAttemptWhenModalMessage()
4300 if (! component.isCurrentlyBlockedByAnotherModalComponent())
4301 return false;
4303 if (auto* current = Component::getCurrentlyModalComponent())
4304 if (auto* owner = getOwnerOfWindow ((HWND) current->getWindowHandle()))
4305 if (! owner->shouldIgnoreModalDismiss)
4306 current->inputAttemptWhenModal();
4308 return true;
4311 //==============================================================================
4312 struct IMEHandler
4314 IMEHandler()
4316 reset();
4319 void handleSetContext (HWND hWnd, const bool windowIsActive)
4321 if (compositionInProgress && ! windowIsActive)
4323 if (HIMC hImc = ImmGetContext (hWnd))
4325 ImmNotifyIME (hImc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
4326 ImmReleaseContext (hWnd, hImc);
4329 // If the composition is still in progress, calling ImmNotifyIME may call back
4330 // into handleComposition to let us know that the composition has finished.
4331 // We need to set compositionInProgress *after* calling handleComposition, so that
4332 // the text replaces the current selection, rather than being inserted after the
4333 // caret.
4334 compositionInProgress = false;
4338 void handleStartComposition (ComponentPeer& owner)
4340 reset();
4342 if (auto* target = owner.findCurrentTextInputTarget())
4343 target->insertTextAtCaret (String());
4346 void handleEndComposition (ComponentPeer& owner, HWND hWnd)
4348 if (compositionInProgress)
4350 // If this occurs, the user has cancelled the composition, so clear their changes..
4351 if (auto* target = owner.findCurrentTextInputTarget())
4353 target->setHighlightedRegion (compositionRange);
4354 target->insertTextAtCaret (String());
4355 compositionRange.setLength (0);
4357 target->setHighlightedRegion (Range<int>::emptyRange (compositionRange.getEnd()));
4358 target->setTemporaryUnderlining ({});
4361 if (auto hImc = ImmGetContext (hWnd))
4363 ImmNotifyIME (hImc, NI_CLOSECANDIDATE, 0, 0);
4364 ImmReleaseContext (hWnd, hImc);
4368 reset();
4371 void handleComposition (ComponentPeer& owner, HWND hWnd, const LPARAM lParam)
4373 if (auto* target = owner.findCurrentTextInputTarget())
4375 if (auto hImc = ImmGetContext (hWnd))
4377 if (compositionRange.getStart() < 0)
4378 compositionRange = Range<int>::emptyRange (target->getHighlightedRegion().getStart());
4380 if ((lParam & GCS_RESULTSTR) != 0) // (composition has finished)
4382 replaceCurrentSelection (target, getCompositionString (hImc, GCS_RESULTSTR),
4383 Range<int>::emptyRange (-1));
4385 reset();
4386 target->setTemporaryUnderlining ({});
4388 else if ((lParam & GCS_COMPSTR) != 0) // (composition is still in-progress)
4390 replaceCurrentSelection (target, getCompositionString (hImc, GCS_COMPSTR),
4391 getCompositionSelection (hImc, lParam));
4393 target->setTemporaryUnderlining (getCompositionUnderlines (hImc, lParam));
4394 compositionInProgress = true;
4397 moveCandidateWindowToLeftAlignWithSelection (hImc, owner, target);
4398 ImmReleaseContext (hWnd, hImc);
4403 private:
4404 //==============================================================================
4405 Range<int> compositionRange; // The range being modified in the TextInputTarget
4406 bool compositionInProgress;
4408 //==============================================================================
4409 void reset()
4411 compositionRange = Range<int>::emptyRange (-1);
4412 compositionInProgress = false;
4415 String getCompositionString (HIMC hImc, const DWORD type) const
4417 jassert (hImc != HIMC{});
4419 const auto stringSizeBytes = ImmGetCompositionString (hImc, type, nullptr, 0);
4421 if (stringSizeBytes > 0)
4423 HeapBlock<TCHAR> buffer;
4424 buffer.calloc ((size_t) stringSizeBytes / sizeof (TCHAR) + 1);
4425 ImmGetCompositionString (hImc, type, buffer, (DWORD) stringSizeBytes);
4426 return String (buffer.get());
4429 return {};
4432 int getCompositionCaretPos (HIMC hImc, LPARAM lParam, const String& currentIMEString) const
4434 jassert (hImc != HIMC{});
4436 if ((lParam & CS_NOMOVECARET) != 0)
4437 return compositionRange.getStart();
4439 if ((lParam & GCS_CURSORPOS) != 0)
4441 const int localCaretPos = ImmGetCompositionString (hImc, GCS_CURSORPOS, nullptr, 0);
4442 return compositionRange.getStart() + jmax (0, localCaretPos);
4445 return compositionRange.getStart() + currentIMEString.length();
4448 // Get selected/highlighted range while doing composition:
4449 // returned range is relative to beginning of TextInputTarget, not composition string
4450 Range<int> getCompositionSelection (HIMC hImc, LPARAM lParam) const
4452 jassert (hImc != HIMC{});
4453 int selectionStart = 0;
4454 int selectionEnd = 0;
4456 if ((lParam & GCS_COMPATTR) != 0)
4458 // Get size of attributes array:
4459 const int attributeSizeBytes = ImmGetCompositionString (hImc, GCS_COMPATTR, nullptr, 0);
4461 if (attributeSizeBytes > 0)
4463 // Get attributes (8 bit flag per character):
4464 HeapBlock<char> attributes (attributeSizeBytes);
4465 ImmGetCompositionString (hImc, GCS_COMPATTR, attributes, (DWORD) attributeSizeBytes);
4467 selectionStart = 0;
4469 for (selectionStart = 0; selectionStart < attributeSizeBytes; ++selectionStart)
4470 if (attributes[selectionStart] == ATTR_TARGET_CONVERTED || attributes[selectionStart] == ATTR_TARGET_NOTCONVERTED)
4471 break;
4473 for (selectionEnd = selectionStart; selectionEnd < attributeSizeBytes; ++selectionEnd)
4474 if (attributes[selectionEnd] != ATTR_TARGET_CONVERTED && attributes[selectionEnd] != ATTR_TARGET_NOTCONVERTED)
4475 break;
4479 return Range<int> (selectionStart, selectionEnd) + compositionRange.getStart();
4482 void replaceCurrentSelection (TextInputTarget* const target, const String& newContent, Range<int> newSelection)
4484 if (compositionInProgress)
4485 target->setHighlightedRegion (compositionRange);
4487 target->insertTextAtCaret (newContent);
4488 compositionRange.setLength (newContent.length());
4490 if (newSelection.getStart() < 0)
4491 newSelection = Range<int>::emptyRange (compositionRange.getEnd());
4493 target->setHighlightedRegion (newSelection);
4496 Array<Range<int>> getCompositionUnderlines (HIMC hImc, LPARAM lParam) const
4498 Array<Range<int>> result;
4500 if (hImc != HIMC{} && (lParam & GCS_COMPCLAUSE) != 0)
4502 auto clauseDataSizeBytes = ImmGetCompositionString (hImc, GCS_COMPCLAUSE, nullptr, 0);
4504 if (clauseDataSizeBytes > 0)
4506 const auto numItems = (size_t) clauseDataSizeBytes / sizeof (uint32);
4507 HeapBlock<uint32> clauseData (numItems);
4509 if (ImmGetCompositionString (hImc, GCS_COMPCLAUSE, clauseData, (DWORD) clauseDataSizeBytes) > 0)
4510 for (size_t i = 0; i + 1 < numItems; ++i)
4511 result.add (Range<int> ((int) clauseData[i], (int) clauseData[i + 1]) + compositionRange.getStart());
4515 return result;
4518 void moveCandidateWindowToLeftAlignWithSelection (HIMC hImc, ComponentPeer& peer, TextInputTarget* target) const
4520 if (auto* targetComp = dynamic_cast<Component*> (target))
4522 auto area = peer.getComponent().getLocalArea (targetComp, target->getCaretRectangle());
4524 CANDIDATEFORM pos = { 0, CFS_CANDIDATEPOS, { area.getX(), area.getBottom() }, { 0, 0, 0, 0 } };
4525 ImmSetCandidateWindow (hImc, &pos);
4529 JUCE_DECLARE_NON_COPYABLE (IMEHandler)
4532 void timerCallback() override
4534 handlePositionChanged();
4535 stopTimer();
4538 static bool isAncestor (HWND outer, HWND inner)
4540 if (outer == nullptr || inner == nullptr)
4541 return false;
4543 if (outer == inner)
4544 return true;
4546 return isAncestor (outer, GetAncestor (inner, GA_PARENT));
4549 void windowShouldDismissModals (HWND originator)
4551 if (shouldIgnoreModalDismiss)
4552 return;
4554 if (isAncestor (originator, hwnd))
4555 sendInputAttemptWhenModalMessage();
4558 // Unfortunately SetWindowsHookEx only allows us to register a static function as a hook.
4559 // To get around this, we keep a static list of listeners which are interested in
4560 // top-level window events, and notify all of these listeners from the callback.
4561 class TopLevelModalDismissBroadcaster
4563 public:
4564 TopLevelModalDismissBroadcaster()
4565 : hook (SetWindowsHookEx (WH_CALLWNDPROC,
4566 callWndProc,
4567 (HINSTANCE) juce::Process::getCurrentModuleInstanceHandle(),
4568 GetCurrentThreadId()))
4571 ~TopLevelModalDismissBroadcaster() noexcept
4573 UnhookWindowsHookEx (hook);
4576 private:
4577 static void processMessage (int nCode, const CWPSTRUCT* info)
4579 if (nCode < 0 || info == nullptr)
4580 return;
4582 constexpr UINT events[] { WM_MOVE,
4583 WM_SIZE,
4584 WM_WINDOWPOSCHANGING,
4585 WM_NCPOINTERDOWN,
4586 WM_NCLBUTTONDOWN,
4587 WM_NCRBUTTONDOWN,
4588 WM_NCMBUTTONDOWN };
4590 if (std::find (std::begin (events), std::end (events), info->message) == std::end (events))
4591 return;
4593 if (info->message == WM_WINDOWPOSCHANGING)
4595 const auto* windowPos = reinterpret_cast<const WINDOWPOS*> (info->lParam);
4596 const auto windowPosFlags = windowPos->flags;
4598 constexpr auto maskToCheck = SWP_NOMOVE | SWP_NOSIZE;
4600 if ((windowPosFlags & maskToCheck) == maskToCheck)
4601 return;
4604 // windowMayDismissModals could affect the number of active ComponentPeer instances
4605 for (auto i = ComponentPeer::getNumPeers(); --i >= 0;)
4606 if (i < ComponentPeer::getNumPeers())
4607 if (auto* hwndPeer = dynamic_cast<HWNDComponentPeer*> (ComponentPeer::getPeer (i)))
4608 hwndPeer->windowShouldDismissModals (info->hwnd);
4611 static LRESULT CALLBACK callWndProc (int nCode, WPARAM wParam, LPARAM lParam)
4613 processMessage (nCode, reinterpret_cast<CWPSTRUCT*> (lParam));
4614 return CallNextHookEx ({}, nCode, wParam, lParam);
4617 HHOOK hook;
4620 SharedResourcePointer<TopLevelModalDismissBroadcaster> modalDismissBroadcaster;
4621 IMEHandler imeHandler;
4622 bool shouldIgnoreModalDismiss = false;
4624 RectangleList<int> deferredRepaints;
4626 //==============================================================================
4627 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HWNDComponentPeer)
4630 MultiTouchMapper<DWORD> HWNDComponentPeer::currentTouches;
4631 ModifierKeys HWNDComponentPeer::modifiersAtLastCallback;
4633 ComponentPeer* Component::createNewPeer (int styleFlags, void* parentHWND)
4635 return new HWNDComponentPeer (*this, styleFlags, (HWND) parentHWND, false);
4638 JUCE_API ComponentPeer* createNonRepaintingEmbeddedWindowsPeer (Component& component, void* parentHWND);
4639 JUCE_API ComponentPeer* createNonRepaintingEmbeddedWindowsPeer (Component& component, void* parentHWND)
4641 return new HWNDComponentPeer (component, ComponentPeer::windowIgnoresMouseClicks,
4642 (HWND) parentHWND, true);
4645 JUCE_IMPLEMENT_SINGLETON (HWNDComponentPeer::WindowClassHolder)
4647 //==============================================================================
4648 bool KeyPress::isKeyCurrentlyDown (const int keyCode)
4650 auto k = (SHORT) keyCode;
4652 if ((keyCode & extendedKeyModifier) == 0)
4654 if (k >= (SHORT) 'a' && k <= (SHORT) 'z')
4655 k += (SHORT) 'A' - (SHORT) 'a';
4657 // Only translate if extendedKeyModifier flag is not set
4658 const SHORT translatedValues[] = { (SHORT) ',', VK_OEM_COMMA,
4659 (SHORT) '+', VK_OEM_PLUS,
4660 (SHORT) '-', VK_OEM_MINUS,
4661 (SHORT) '.', VK_OEM_PERIOD,
4662 (SHORT) ';', VK_OEM_1,
4663 (SHORT) ':', VK_OEM_1,
4664 (SHORT) '/', VK_OEM_2,
4665 (SHORT) '?', VK_OEM_2,
4666 (SHORT) '[', VK_OEM_4,
4667 (SHORT) ']', VK_OEM_6 };
4669 for (int i = 0; i < numElementsInArray (translatedValues); i += 2)
4670 if (k == translatedValues[i])
4671 k = translatedValues[i + 1];
4674 return HWNDComponentPeer::isKeyDown (k);
4677 // (This internal function is used by the plugin client module)
4678 bool offerKeyMessageToJUCEWindow (MSG& m);
4679 bool offerKeyMessageToJUCEWindow (MSG& m) { return HWNDComponentPeer::offerKeyMessageToJUCEWindow (m); }
4681 //==============================================================================
4682 static DWORD getProcess (HWND hwnd)
4684 DWORD result = 0;
4685 GetWindowThreadProcessId (hwnd, &result);
4686 return result;
4689 /* Returns true if the viewComponent is embedded into a window
4690 owned by the foreground process.
4692 bool isEmbeddedInForegroundProcess (Component* c)
4694 if (c == nullptr)
4695 return false;
4697 auto* peer = c->getPeer();
4698 auto* hwnd = peer != nullptr ? static_cast<HWND> (peer->getNativeHandle()) : nullptr;
4700 if (hwnd == nullptr)
4701 return true;
4703 const auto fgProcess = getProcess (GetForegroundWindow());
4704 const auto ownerProcess = getProcess (GetAncestor (hwnd, GA_ROOTOWNER));
4705 return fgProcess == ownerProcess;
4708 bool JUCE_CALLTYPE Process::isForegroundProcess()
4710 if (auto fg = GetForegroundWindow())
4711 return getProcess (fg) == GetCurrentProcessId();
4713 return true;
4716 // N/A on Windows as far as I know.
4717 void JUCE_CALLTYPE Process::makeForegroundProcess() {}
4718 void JUCE_CALLTYPE Process::hide() {}
4720 //==============================================================================
4721 static BOOL CALLBACK enumAlwaysOnTopWindows (HWND hwnd, LPARAM lParam)
4723 if (IsWindowVisible (hwnd))
4725 DWORD processID = 0;
4726 GetWindowThreadProcessId (hwnd, &processID);
4728 if (processID == GetCurrentProcessId())
4730 WINDOWINFO info{};
4732 if (GetWindowInfo (hwnd, &info)
4733 && (info.dwExStyle & WS_EX_TOPMOST) != 0)
4735 *reinterpret_cast<bool*> (lParam) = true;
4736 return FALSE;
4741 return TRUE;
4744 bool juce_areThereAnyAlwaysOnTopWindows()
4746 bool anyAlwaysOnTopFound = false;
4747 EnumWindows (&enumAlwaysOnTopWindows, (LPARAM) &anyAlwaysOnTopFound);
4748 return anyAlwaysOnTopFound;
4751 //==============================================================================
4752 #if JUCE_MSVC
4753 // required to enable the newer dialog box on vista and above
4754 #pragma comment(linker, \
4755 "\"/MANIFESTDEPENDENCY:type='Win32' " \
4756 "name='Microsoft.Windows.Common-Controls' " \
4757 "version='6.0.0.0' " \
4758 "processorArchitecture='*' " \
4759 "publicKeyToken='6595b64144ccf1df' " \
4760 "language='*'\"" \
4762 #endif
4764 class WindowsMessageBoxBase : private AsyncUpdater
4766 public:
4767 WindowsMessageBoxBase (Component* comp,
4768 std::unique_ptr<ModalComponentManager::Callback>&& cb)
4769 : associatedComponent (comp),
4770 callback (std::move (cb))
4774 virtual int getResult() = 0;
4776 HWND getParentHWND() const
4778 if (associatedComponent != nullptr)
4779 return (HWND) associatedComponent->getWindowHandle();
4781 return nullptr;
4784 using AsyncUpdater::triggerAsyncUpdate;
4786 private:
4787 void handleAsyncUpdate() override
4789 const auto result = getResult();
4791 if (callback != nullptr)
4792 callback->modalStateFinished (result);
4794 delete this;
4797 Component::SafePointer<Component> associatedComponent;
4798 std::unique_ptr<ModalComponentManager::Callback> callback;
4800 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsMessageBoxBase)
4803 class PreVistaMessageBox : public WindowsMessageBoxBase
4805 public:
4806 PreVistaMessageBox (const MessageBoxOptions& opts,
4807 UINT extraFlags,
4808 std::unique_ptr<ModalComponentManager::Callback>&& cb)
4809 : WindowsMessageBoxBase (opts.getAssociatedComponent(), std::move (cb)),
4810 flags (extraFlags | getMessageBoxFlags (opts.getIconType())),
4811 title (opts.getTitle()), message (opts.getMessage())
4815 int getResult() override
4817 const auto result = MessageBox (getParentHWND(), message.toWideCharPointer(), title.toWideCharPointer(), flags);
4819 if (result == IDYES || result == IDOK) return 0;
4820 if (result == IDNO && ((flags & 1) != 0)) return 1;
4822 return 2;
4825 private:
4826 static UINT getMessageBoxFlags (MessageBoxIconType iconType) noexcept
4828 // this window can get lost behind JUCE windows which are set to be alwaysOnTop
4829 // so if there are any set it to be topmost
4830 const auto topmostFlag = juce_areThereAnyAlwaysOnTopWindows() ? MB_TOPMOST : 0;
4832 const auto iconFlags = [&]() -> decltype (topmostFlag)
4834 switch (iconType)
4836 case MessageBoxIconType::QuestionIcon: return MB_ICONQUESTION;
4837 case MessageBoxIconType::WarningIcon: return MB_ICONWARNING;
4838 case MessageBoxIconType::InfoIcon: return MB_ICONINFORMATION;
4839 case MessageBoxIconType::NoIcon: break;
4842 return 0;
4843 }();
4845 return static_cast<UINT> (MB_TASKMODAL | MB_SETFOREGROUND | topmostFlag | iconFlags);
4848 const UINT flags;
4849 const String title, message;
4851 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PreVistaMessageBox)
4854 using TaskDialogIndirectFunc = HRESULT (WINAPI*) (const TASKDIALOGCONFIG*, INT*, INT*, BOOL*);
4855 static TaskDialogIndirectFunc taskDialogIndirect = nullptr;
4857 class WindowsTaskDialog : public WindowsMessageBoxBase
4859 public:
4860 WindowsTaskDialog (const MessageBoxOptions& opts,
4861 std::unique_ptr<ModalComponentManager::Callback>&& cb)
4862 : WindowsMessageBoxBase (opts.getAssociatedComponent(), std::move (cb)),
4863 iconType (opts.getIconType()),
4864 title (opts.getTitle()), message (opts.getMessage()),
4865 button1 (opts.getButtonText (0)), button2 (opts.getButtonText (1)), button3 (opts.getButtonText (2))
4869 int getResult() override
4871 TASKDIALOGCONFIG config{};
4873 config.cbSize = sizeof (config);
4874 config.hwndParent = getParentHWND();
4875 config.pszWindowTitle = title.toWideCharPointer();
4876 config.pszContent = message.toWideCharPointer();
4877 config.hInstance = (HINSTANCE) Process::getCurrentModuleInstanceHandle();
4879 if (iconType == MessageBoxIconType::QuestionIcon)
4881 if (auto* questionIcon = LoadIcon (nullptr, IDI_QUESTION))
4883 config.hMainIcon = questionIcon;
4884 config.dwFlags |= TDF_USE_HICON_MAIN;
4887 else
4889 auto icon = [this]() -> LPWSTR
4891 switch (iconType)
4893 case MessageBoxIconType::WarningIcon: return TD_WARNING_ICON;
4894 case MessageBoxIconType::InfoIcon: return TD_INFORMATION_ICON;
4896 case MessageBoxIconType::QuestionIcon: JUCE_FALLTHROUGH
4897 case MessageBoxIconType::NoIcon:
4898 break;
4901 return nullptr;
4902 }();
4904 if (icon != nullptr)
4905 config.pszMainIcon = icon;
4908 std::vector<TASKDIALOG_BUTTON> buttons;
4910 for (const auto* buttonText : { &button1, &button2, &button3 })
4911 if (buttonText->isNotEmpty())
4912 buttons.push_back ({ (int) buttons.size(), buttonText->toWideCharPointer() });
4914 config.pButtons = buttons.data();
4915 config.cButtons = (UINT) buttons.size();
4917 int buttonIndex = 0;
4918 taskDialogIndirect (&config, &buttonIndex, nullptr, nullptr);
4920 return buttonIndex;
4923 static bool loadTaskDialog()
4925 static bool hasChecked = false;
4927 if (! hasChecked)
4929 hasChecked = true;
4931 const auto comctl = "Comctl32.dll";
4932 LoadLibraryA (comctl);
4933 const auto comctlModule = GetModuleHandleA (comctl);
4935 if (comctlModule != nullptr)
4936 taskDialogIndirect = (TaskDialogIndirectFunc) GetProcAddress (comctlModule, "TaskDialogIndirect");
4939 return taskDialogIndirect != nullptr;
4942 private:
4943 MessageBoxIconType iconType;
4944 String title, message, button1, button2, button3;
4946 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsTaskDialog)
4949 static std::unique_ptr<WindowsMessageBoxBase> createMessageBox (const MessageBoxOptions& options,
4950 std::unique_ptr<ModalComponentManager::Callback> callback)
4952 const auto useTaskDialog =
4953 #if JUCE_MODAL_LOOPS_PERMITTED
4954 callback != nullptr &&
4955 #endif
4956 SystemStats::getOperatingSystemType() >= SystemStats::WinVista
4957 && WindowsTaskDialog::loadTaskDialog();
4959 if (useTaskDialog)
4960 return std::make_unique<WindowsTaskDialog> (options, std::move (callback));
4962 const auto extraFlags = [&options]
4964 const auto numButtons = options.getNumButtons();
4966 if (numButtons == 3)
4967 return MB_YESNOCANCEL;
4969 if (numButtons == 2)
4970 return options.getButtonText (0) == "OK" ? MB_OKCANCEL
4971 : MB_YESNO;
4973 return MB_OK;
4974 }();
4976 return std::make_unique<PreVistaMessageBox> (options, (UINT) extraFlags, std::move (callback));
4979 static int showDialog (const MessageBoxOptions& options,
4980 ModalComponentManager::Callback* callbackIn,
4981 AlertWindowMappings::MapFn mapFn)
4983 #if JUCE_MODAL_LOOPS_PERMITTED
4984 if (callbackIn == nullptr)
4986 jassert (mapFn != nullptr);
4988 auto messageBox = createMessageBox (options, nullptr);
4989 return mapFn (messageBox->getResult());
4991 #endif
4993 auto messageBox = createMessageBox (options,
4994 AlertWindowMappings::getWrappedCallback (callbackIn, mapFn));
4996 messageBox->triggerAsyncUpdate();
4997 messageBox.release();
4999 return 0;
5002 #if JUCE_MODAL_LOOPS_PERMITTED
5003 void JUCE_CALLTYPE NativeMessageBox::showMessageBox (MessageBoxIconType iconType,
5004 const String& title, const String& message,
5005 Component* associatedComponent)
5007 showDialog (MessageBoxOptions()
5008 .withIconType (iconType)
5009 .withTitle (title)
5010 .withMessage (message)
5011 .withButton (TRANS("OK"))
5012 .withAssociatedComponent (associatedComponent),
5013 nullptr, AlertWindowMappings::messageBox);
5016 int JUCE_CALLTYPE NativeMessageBox::show (const MessageBoxOptions& options)
5018 return showDialog (options, nullptr, AlertWindowMappings::noMapping);
5020 #endif
5022 void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (MessageBoxIconType iconType,
5023 const String& title, const String& message,
5024 Component* associatedComponent,
5025 ModalComponentManager::Callback* callback)
5027 showDialog (MessageBoxOptions()
5028 .withIconType (iconType)
5029 .withTitle (title)
5030 .withMessage (message)
5031 .withButton (TRANS("OK"))
5032 .withAssociatedComponent (associatedComponent),
5033 callback, AlertWindowMappings::messageBox);
5036 bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (MessageBoxIconType iconType,
5037 const String& title, const String& message,
5038 Component* associatedComponent,
5039 ModalComponentManager::Callback* callback)
5041 return showDialog (MessageBoxOptions()
5042 .withIconType (iconType)
5043 .withTitle (title)
5044 .withMessage (message)
5045 .withButton (TRANS("OK"))
5046 .withButton (TRANS("Cancel"))
5047 .withAssociatedComponent (associatedComponent),
5048 callback, AlertWindowMappings::okCancel) != 0;
5051 int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (MessageBoxIconType iconType,
5052 const String& title, const String& message,
5053 Component* associatedComponent,
5054 ModalComponentManager::Callback* callback)
5056 return showDialog (MessageBoxOptions()
5057 .withIconType (iconType)
5058 .withTitle (title)
5059 .withMessage (message)
5060 .withButton (TRANS("Yes"))
5061 .withButton (TRANS("No"))
5062 .withButton (TRANS("Cancel"))
5063 .withAssociatedComponent (associatedComponent),
5064 callback, AlertWindowMappings::yesNoCancel);
5067 int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (MessageBoxIconType iconType,
5068 const String& title, const String& message,
5069 Component* associatedComponent,
5070 ModalComponentManager::Callback* callback)
5072 return showDialog (MessageBoxOptions()
5073 .withIconType (iconType)
5074 .withTitle (title)
5075 .withMessage (message)
5076 .withButton (TRANS("Yes"))
5077 .withButton (TRANS("No"))
5078 .withAssociatedComponent (associatedComponent),
5079 callback, AlertWindowMappings::okCancel);
5082 void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
5083 ModalComponentManager::Callback* callback)
5085 showDialog (options, callback, AlertWindowMappings::noMapping);
5088 void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
5089 std::function<void (int)> callback)
5091 showAsync (options, ModalCallbackFunction::create (callback));
5094 //==============================================================================
5095 bool MouseInputSource::SourceList::addSource()
5097 auto numSources = sources.size();
5099 if (numSources == 0 || canUseMultiTouch())
5101 addSource (numSources, numSources == 0 ? MouseInputSource::InputSourceType::mouse
5102 : MouseInputSource::InputSourceType::touch);
5103 return true;
5106 return false;
5109 bool MouseInputSource::SourceList::canUseTouch()
5111 return canUseMultiTouch();
5114 Point<float> MouseInputSource::getCurrentRawMousePosition()
5116 POINT mousePos;
5117 GetCursorPos (&mousePos);
5119 auto p = pointFromPOINT (mousePos);
5121 if (isPerMonitorDPIAwareThread())
5122 p = Desktop::getInstance().getDisplays().physicalToLogical (p);
5124 return p.toFloat();
5127 void MouseInputSource::setRawMousePosition (Point<float> newPosition)
5129 auto newPositionInt = newPosition.roundToInt();
5131 #if JUCE_WIN_PER_MONITOR_DPI_AWARE
5132 if (isPerMonitorDPIAwareThread())
5133 newPositionInt = Desktop::getInstance().getDisplays().logicalToPhysical (newPositionInt);
5134 #endif
5136 auto point = POINTFromPoint (newPositionInt);
5137 SetCursorPos (point.x, point.y);
5140 //==============================================================================
5141 class ScreenSaverDefeater : public Timer
5143 public:
5144 ScreenSaverDefeater()
5146 startTimer (10000);
5147 timerCallback();
5150 void timerCallback() override
5152 if (Process::isForegroundProcess())
5154 INPUT input = {};
5155 input.type = INPUT_MOUSE;
5156 input.mi.mouseData = MOUSEEVENTF_MOVE;
5158 SendInput (1, &input, sizeof (INPUT));
5163 static std::unique_ptr<ScreenSaverDefeater> screenSaverDefeater;
5165 void Desktop::setScreenSaverEnabled (const bool isEnabled)
5167 if (isEnabled)
5168 screenSaverDefeater = nullptr;
5169 else if (screenSaverDefeater == nullptr)
5170 screenSaverDefeater.reset (new ScreenSaverDefeater());
5173 bool Desktop::isScreenSaverEnabled()
5175 return screenSaverDefeater == nullptr;
5178 //==============================================================================
5179 void LookAndFeel::playAlertSound()
5181 MessageBeep (MB_OK);
5184 //==============================================================================
5185 void SystemClipboard::copyTextToClipboard (const String& text)
5187 if (OpenClipboard (nullptr) != 0)
5189 if (EmptyClipboard() != 0)
5191 auto bytesNeeded = CharPointer_UTF16::getBytesRequiredFor (text.getCharPointer()) + 4;
5193 if (bytesNeeded > 0)
5195 if (auto bufH = GlobalAlloc (GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT, bytesNeeded + sizeof (WCHAR)))
5197 if (auto* data = static_cast<WCHAR*> (GlobalLock (bufH)))
5199 text.copyToUTF16 (data, bytesNeeded);
5200 GlobalUnlock (bufH);
5202 SetClipboardData (CF_UNICODETEXT, bufH);
5208 CloseClipboard();
5212 String SystemClipboard::getTextFromClipboard()
5214 String result;
5216 if (OpenClipboard (nullptr) != 0)
5218 if (auto bufH = GetClipboardData (CF_UNICODETEXT))
5220 if (auto* data = (const WCHAR*) GlobalLock (bufH))
5222 result = String (data, (size_t) (GlobalSize (bufH) / sizeof (WCHAR)));
5223 GlobalUnlock (bufH);
5227 CloseClipboard();
5230 return result;
5233 //==============================================================================
5234 void Desktop::setKioskComponent (Component* kioskModeComp, bool enableOrDisable, bool /*allowMenusAndBars*/)
5236 if (auto* tlw = dynamic_cast<TopLevelWindow*> (kioskModeComp))
5237 tlw->setUsingNativeTitleBar (! enableOrDisable);
5239 if (kioskModeComp != nullptr && enableOrDisable)
5240 kioskModeComp->setBounds (getDisplays().getDisplayForRect (kioskModeComp->getScreenBounds())->totalArea);
5243 void Desktop::allowedOrientationsChanged() {}
5245 //==============================================================================
5246 static const Displays::Display* getCurrentDisplayFromScaleFactor (HWND hwnd)
5248 Array<const Displays::Display*> candidateDisplays;
5250 const auto scaleToLookFor = [&]
5252 if (auto* peer = HWNDComponentPeer::getOwnerOfWindow (hwnd))
5253 return peer->getPlatformScaleFactor();
5255 return getScaleFactorForWindow (hwnd);
5256 }();
5258 auto globalScale = Desktop::getInstance().getGlobalScaleFactor();
5260 for (auto& d : Desktop::getInstance().getDisplays().displays)
5261 if (approximatelyEqual (d.scale / globalScale, scaleToLookFor))
5262 candidateDisplays.add (&d);
5264 if (candidateDisplays.size() > 0)
5266 if (candidateDisplays.size() == 1)
5267 return candidateDisplays[0];
5269 const auto bounds = [&]
5271 if (auto* peer = HWNDComponentPeer::getOwnerOfWindow (hwnd))
5272 return peer->getComponent().getTopLevelComponent()->getBounds();
5274 return Desktop::getInstance().getDisplays().physicalToLogical (rectangleFromRECT (getWindowScreenRect (hwnd)));
5275 }();
5277 const Displays::Display* retVal = nullptr;
5278 int maxArea = -1;
5280 for (auto* d : candidateDisplays)
5282 auto intersection = d->totalArea.getIntersection (bounds);
5283 auto area = intersection.getWidth() * intersection.getHeight();
5285 if (area > maxArea)
5287 maxArea = area;
5288 retVal = d;
5292 if (retVal != nullptr)
5293 return retVal;
5296 return Desktop::getInstance().getDisplays().getPrimaryDisplay();
5299 //==============================================================================
5300 struct MonitorInfo
5302 MonitorInfo (bool main, RECT totalArea, RECT workArea, double d) noexcept
5303 : isMain (main),
5304 totalAreaRect (totalArea),
5305 workAreaRect (workArea),
5306 dpi (d)
5310 bool isMain;
5311 RECT totalAreaRect, workAreaRect;
5312 double dpi;
5315 static BOOL CALLBACK enumMonitorsProc (HMONITOR hm, HDC, LPRECT, LPARAM userInfo)
5317 MONITORINFO info = {};
5318 info.cbSize = sizeof (info);
5319 GetMonitorInfo (hm, &info);
5321 auto isMain = (info.dwFlags & 1 /* MONITORINFOF_PRIMARY */) != 0;
5322 auto dpi = 0.0;
5324 if (getDPIForMonitor != nullptr)
5326 UINT dpiX = 0, dpiY = 0;
5328 if (SUCCEEDED (getDPIForMonitor (hm, MDT_Default, &dpiX, &dpiY)))
5329 dpi = (dpiX + dpiY) / 2.0;
5332 ((Array<MonitorInfo>*) userInfo)->add ({ isMain, info.rcMonitor, info.rcWork, dpi });
5333 return TRUE;
5336 void Displays::findDisplays (float masterScale)
5338 setDPIAwareness();
5340 Array<MonitorInfo> monitors;
5341 EnumDisplayMonitors (nullptr, nullptr, &enumMonitorsProc, (LPARAM) &monitors);
5343 auto globalDPI = getGlobalDPI();
5345 if (monitors.size() == 0)
5347 auto windowRect = getWindowScreenRect (GetDesktopWindow());
5348 monitors.add ({ true, windowRect, windowRect, globalDPI });
5351 // make sure the first in the list is the main monitor
5352 for (int i = 1; i < monitors.size(); ++i)
5353 if (monitors.getReference (i).isMain)
5354 monitors.swap (i, 0);
5356 for (auto& monitor : monitors)
5358 Display d;
5360 d.isMain = monitor.isMain;
5361 d.dpi = monitor.dpi;
5363 if (d.dpi == 0)
5365 d.dpi = globalDPI;
5366 d.scale = masterScale;
5368 else
5370 d.scale = (d.dpi / USER_DEFAULT_SCREEN_DPI) * (masterScale / Desktop::getDefaultMasterScale());
5373 d.totalArea = rectangleFromRECT (monitor.totalAreaRect);
5374 d.userArea = rectangleFromRECT (monitor.workAreaRect);
5376 displays.add (d);
5379 #if JUCE_WIN_PER_MONITOR_DPI_AWARE
5380 if (isPerMonitorDPIAwareThread())
5381 updateToLogical();
5382 else
5383 #endif
5385 for (auto& d : displays)
5387 d.totalArea /= masterScale;
5388 d.userArea /= masterScale;
5393 //==============================================================================
5394 static HICON extractFileHICON (const File& file)
5396 WORD iconNum = 0;
5397 WCHAR name[MAX_PATH * 2];
5398 file.getFullPathName().copyToUTF16 (name, sizeof (name));
5400 return ExtractAssociatedIcon ((HINSTANCE) Process::getCurrentModuleInstanceHandle(),
5401 name, &iconNum);
5404 Image juce_createIconForFile (const File& file)
5406 Image image;
5408 if (auto icon = extractFileHICON (file))
5410 image = IconConverters::createImageFromHICON (icon);
5411 DestroyIcon (icon);
5414 return image;
5417 //==============================================================================
5418 class MouseCursor::PlatformSpecificHandle
5420 public:
5421 explicit PlatformSpecificHandle (const MouseCursor::StandardCursorType type)
5422 : impl (makeHandle (type)) {}
5424 explicit PlatformSpecificHandle (const CustomMouseCursorInfo& info)
5425 : impl (makeHandle (info)) {}
5427 static void showInWindow (PlatformSpecificHandle* handle, ComponentPeer* peer)
5429 SetCursor ([&]
5431 if (handle != nullptr && handle->impl != nullptr && peer != nullptr)
5432 return handle->impl->getCursor (*peer);
5434 return LoadCursor (nullptr, IDC_ARROW);
5435 }());
5438 private:
5439 struct Impl
5441 virtual ~Impl() = default;
5442 virtual HCURSOR getCursor (ComponentPeer&) = 0;
5445 class BuiltinImpl : public Impl
5447 public:
5448 explicit BuiltinImpl (HCURSOR cursorIn)
5449 : cursor (cursorIn) {}
5451 HCURSOR getCursor (ComponentPeer&) override { return cursor; }
5453 private:
5454 HCURSOR cursor;
5457 class ImageImpl : public Impl
5459 public:
5460 explicit ImageImpl (const CustomMouseCursorInfo& infoIn) : info (infoIn) {}
5462 ~ImageImpl() override
5464 for (auto& pair : cursorsBySize)
5465 DestroyCursor (pair.second);
5468 HCURSOR getCursor (ComponentPeer& peer) override
5470 JUCE_ASSERT_MESSAGE_THREAD;
5472 static auto getCursorSize = getCursorSizeForPeerFunction();
5474 const auto size = getCursorSize (peer);
5475 const auto iter = cursorsBySize.find (size);
5477 if (iter != cursorsBySize.end())
5478 return iter->second;
5480 const auto logicalSize = info.image.getScaledBounds();
5481 const auto scale = (float) size / (float) unityCursorSize;
5482 const auto physicalSize = logicalSize * scale;
5484 const auto& image = info.image.getImage();
5485 const auto rescaled = image.rescaled (roundToInt ((float) physicalSize.getWidth()),
5486 roundToInt ((float) physicalSize.getHeight()));
5488 const auto effectiveScale = rescaled.getWidth() / logicalSize.getWidth();
5490 const auto hx = jlimit (0, rescaled.getWidth(), roundToInt ((float) info.hotspot.x * effectiveScale));
5491 const auto hy = jlimit (0, rescaled.getHeight(), roundToInt ((float) info.hotspot.y * effectiveScale));
5493 return cursorsBySize.emplace (size, IconConverters::createHICONFromImage (rescaled, false, hx, hy)).first->second;
5496 private:
5497 const CustomMouseCursorInfo info;
5498 std::map<int, HCURSOR> cursorsBySize;
5501 static auto getCursorSizeForPeerFunction() -> int (*) (ComponentPeer&)
5503 static const auto getDpiForMonitor = []() -> GetDPIForMonitorFunc
5505 constexpr auto library = "SHCore.dll";
5506 LoadLibraryA (library);
5508 if (auto* handle = GetModuleHandleA (library))
5509 return (GetDPIForMonitorFunc) GetProcAddress (handle, "GetDpiForMonitor");
5511 return nullptr;
5512 }();
5514 static const auto getSystemMetricsForDpi = []() -> GetSystemMetricsForDpiFunc
5516 constexpr auto library = "User32.dll";
5517 LoadLibraryA (library);
5519 if (auto* handle = GetModuleHandleA (library))
5520 return (GetSystemMetricsForDpiFunc) GetProcAddress (handle, "GetSystemMetricsForDpi");
5522 return nullptr;
5523 }();
5525 if (getDpiForMonitor == nullptr || getSystemMetricsForDpi == nullptr)
5526 return [] (ComponentPeer&) { return unityCursorSize; };
5528 return [] (ComponentPeer& p)
5530 const ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { p.getNativeHandle() };
5532 UINT dpiX = 0, dpiY = 0;
5534 if (auto* monitor = MonitorFromWindow ((HWND) p.getNativeHandle(), MONITOR_DEFAULTTONULL))
5535 if (SUCCEEDED (getDpiForMonitor (monitor, MDT_Default, &dpiX, &dpiY)))
5536 return getSystemMetricsForDpi (SM_CXCURSOR, dpiX);
5538 return unityCursorSize;
5542 static constexpr auto unityCursorSize = 32;
5544 static std::unique_ptr<Impl> makeHandle (const CustomMouseCursorInfo& info)
5546 return std::make_unique<ImageImpl> (info);
5549 static std::unique_ptr<Impl> makeHandle (const MouseCursor::StandardCursorType type)
5551 LPCTSTR cursorName = IDC_ARROW;
5553 switch (type)
5555 case NormalCursor:
5556 case ParentCursor: break;
5557 case NoCursor: return std::make_unique<BuiltinImpl> (nullptr);
5558 case WaitCursor: cursorName = IDC_WAIT; break;
5559 case IBeamCursor: cursorName = IDC_IBEAM; break;
5560 case PointingHandCursor: cursorName = MAKEINTRESOURCE(32649); break;
5561 case CrosshairCursor: cursorName = IDC_CROSS; break;
5563 case LeftRightResizeCursor:
5564 case LeftEdgeResizeCursor:
5565 case RightEdgeResizeCursor: cursorName = IDC_SIZEWE; break;
5567 case UpDownResizeCursor:
5568 case TopEdgeResizeCursor:
5569 case BottomEdgeResizeCursor: cursorName = IDC_SIZENS; break;
5571 case TopLeftCornerResizeCursor:
5572 case BottomRightCornerResizeCursor: cursorName = IDC_SIZENWSE; break;
5574 case TopRightCornerResizeCursor:
5575 case BottomLeftCornerResizeCursor: cursorName = IDC_SIZENESW; break;
5577 case UpDownLeftRightResizeCursor: cursorName = IDC_SIZEALL; break;
5579 case DraggingHandCursor:
5581 static const unsigned char dragHandData[]
5582 { 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,
5583 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,
5584 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 };
5586 return makeHandle ({ ScaledImage (ImageFileFormat::loadFrom (dragHandData, sizeof (dragHandData))), { 8, 7 } });
5589 case CopyingCursor:
5591 static const unsigned char copyCursorData[]
5592 { 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,
5593 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,
5594 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,
5595 5,50,68,147,208,217,16,71,149,252,124,5,0,59,0,0 };
5597 return makeHandle ({ ScaledImage (ImageFileFormat::loadFrom (copyCursorData, sizeof (copyCursorData))), { 1, 3 } });
5600 case NumStandardCursorTypes: JUCE_FALLTHROUGH
5601 default:
5602 jassertfalse; break;
5605 return std::make_unique<BuiltinImpl> ([&]
5607 if (auto* c = LoadCursor (nullptr, cursorName))
5608 return c;
5610 return LoadCursor (nullptr, IDC_ARROW);
5611 }());
5614 std::unique_ptr<Impl> impl;
5617 //==============================================================================
5618 JUCE_END_IGNORE_WARNINGS_GCC_LIKE
5620 } // namespace juce