Blender V4.5
GHOST_SystemWin32.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include <limits>
10#include <map>
11
14#include "GHOST_SystemWin32.hh"
15
16#ifndef _WIN32_IE
17# define _WIN32_IE 0x0501 /* shipped before XP, so doesn't impose additional requirements */
18#endif
19
20#include <commctrl.h>
21#include <dwmapi.h>
22#include <psapi.h>
23#include <shellapi.h>
24#include <shellscalingapi.h>
25#include <shlobj.h>
26#include <tlhelp32.h>
27#include <windowsx.h>
28
29#include "utf_winfunc.hh"
30#include "utfconv.hh"
31
32#include "IMB_imbuf.hh"
33#include "IMB_imbuf_types.hh"
34
35#include "GHOST_EventButton.hh"
36#include "GHOST_EventCursor.hh"
37#include "GHOST_EventKey.hh"
38#include "GHOST_EventWheel.hh"
39#include "GHOST_TimerManager.hh"
40#include "GHOST_TimerTask.hh"
42#include "GHOST_WindowWin32.hh"
43
44#include "GHOST_ContextD3D.hh"
45#ifdef WITH_OPENGL_BACKEND
46# include "GHOST_ContextWGL.hh"
47#endif
48#ifdef WITH_VULKAN_BACKEND
49# include "GHOST_ContextVK.hh"
50#endif
51
52#ifdef WITH_INPUT_NDOF
54#endif
55
56/* Key code values not found in `winuser.h`. */
57#ifndef VK_MINUS
58# define VK_MINUS 0xBD
59#endif /* VK_MINUS */
60#ifndef VK_SEMICOLON
61# define VK_SEMICOLON 0xBA
62#endif /* VK_SEMICOLON */
63#ifndef VK_PERIOD
64# define VK_PERIOD 0xBE
65#endif /* VK_PERIOD */
66#ifndef VK_COMMA
67# define VK_COMMA 0xBC
68#endif /* VK_COMMA */
69#ifndef VK_BACK_QUOTE
70# define VK_BACK_QUOTE 0xC0
71#endif /* VK_BACK_QUOTE */
72#ifndef VK_SLASH
73# define VK_SLASH 0xBF
74#endif /* VK_SLASH */
75#ifndef VK_BACK_SLASH
76# define VK_BACK_SLASH 0xDC
77#endif /* VK_BACK_SLASH */
78#ifndef VK_EQUALS
79# define VK_EQUALS 0xBB
80#endif /* VK_EQUALS */
81#ifndef VK_OPEN_BRACKET
82# define VK_OPEN_BRACKET 0xDB
83#endif /* VK_OPEN_BRACKET */
84#ifndef VK_CLOSE_BRACKET
85# define VK_CLOSE_BRACKET 0xDD
86#endif /* VK_CLOSE_BRACKET */
87#ifndef VK_GR_LESS
88# define VK_GR_LESS 0xE2
89#endif /* VK_GR_LESS */
90
101#define BROKEN_PEEK_TOUCHPAD
102
103static bool isStartedFromCommandPrompt();
104
122
124{
125 const auto iter = longButtonHIDsToGHOST_NDOFButtons.find(longKey);
126 if (iter == longButtonHIDsToGHOST_NDOFButtons.end()) {
127 return static_cast<GHOST_NDOF_ButtonT>(longKey);
128 }
129 return iter->second;
130}
131
132static void initRawInput()
133{
134#ifdef WITH_INPUT_NDOF
135# define DEVICE_COUNT 2
136#else
137# define DEVICE_COUNT 1
138#endif
139
140 RAWINPUTDEVICE devices[DEVICE_COUNT];
141 memset(devices, 0, DEVICE_COUNT * sizeof(RAWINPUTDEVICE));
142
143 /* Initiates WM_INPUT messages from keyboard
144 * That way GHOST can retrieve true keys. */
145 devices[0].usUsagePage = 0x01;
146 devices[0].usUsage = 0x06; /* http://msdn.microsoft.com/en-us/windows/hardware/gg487473.aspx */
147
148#ifdef WITH_INPUT_NDOF
149 /* multi-axis mouse (SpaceNavigator, etc.). */
150 devices[1].usUsagePage = 0x01;
151 devices[1].usUsage = 0x08;
152#endif
153
154 if (RegisterRawInputDevices(devices, DEVICE_COUNT, sizeof(RAWINPUTDEVICE))) {
155 /* Success. */
156 }
157 else {
158 GHOST_PRINTF("could not register for RawInput: %d\n", int(GetLastError()));
159 }
160#undef DEVICE_COUNT
161}
162
164
166{
167 m_consoleStatus = true;
168
169 /* Tell Windows we are per monitor DPI aware. This disables the default
170 * blurry scaling and enables WM_DPICHANGED to allow us to draw at proper DPI. */
171 SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
172
173 /* Set App Id for the process so our console will be grouped on the Task Bar. */
174 UTF16_ENCODE(BLENDER_WIN_APPID);
175 SetCurrentProcessExplicitAppUserModelID(BLENDER_WIN_APPID_16);
176 UTF16_UN_ENCODE(BLENDER_WIN_APPID);
177
178 /* Check if current keyboard layout uses AltGr and save keylayout ID for
179 * specialized handling if keys like VK_OEM_*. I.e. french keylayout
180 * generates #VK_OEM_8 for their exclamation key (key left of right shift). */
181 this->handleKeyboardChange();
182 /* Require COM for GHOST_DropTargetWin32 created in GHOST_WindowWin32. */
183 OleInitialize(0);
184
185#ifdef WITH_INPUT_NDOF
186 m_ndofManager = new GHOST_NDOFManagerWin32(*this);
187#endif
188}
189
191{
192 /* Shutdown COM. */
193 OleUninitialize();
194
197 }
198}
199
201{
202 /* Calculate the time passed since system initialization. */
203 __int64 delta = perf_ticks * 1000;
204
205 uint64_t t = uint64_t(delta / m_freq);
206 return t;
207}
208
210{
211 /* Hardware does not support high resolution timers. We will use GetTickCount instead then. */
213 return ::GetTickCount64();
214 }
215
216 /* Retrieve current count */
217 __int64 count = 0;
218 ::QueryPerformanceCounter((LARGE_INTEGER *)&count);
219
221}
222
229{
230 /* Get difference between last message time and now. */
231 int64_t t_delta = GetMessageTime() - GetTickCount();
232
233 /* Handle 32-bit rollover. */
234 if (t_delta > 0) {
235 t_delta -= int64_t(UINT32_MAX) + 1;
236 }
237
238 /* Return message time as 64-bit milliseconds with the delta applied. */
239 return system->getMilliSeconds() + t_delta;
240}
241
243{
244 return ::GetSystemMetrics(SM_CMONITORS);
245}
246
247void GHOST_SystemWin32::getMainDisplayDimensions(uint32_t &width, uint32_t &height) const
248{
249 width = ::GetSystemMetrics(SM_CXSCREEN);
250 height = ::GetSystemMetrics(SM_CYSCREEN);
251}
252
253void GHOST_SystemWin32::getAllDisplayDimensions(uint32_t &width, uint32_t &height) const
254{
255 width = ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
256 height = ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
257}
258
261 int32_t top,
262 uint32_t width,
263 uint32_t height,
265 GHOST_GPUSettings gpuSettings,
266 const bool /*exclusive*/,
267 const bool is_dialog,
268 const GHOST_IWindow *parentWindow)
269{
271 this,
272 title,
273 left,
274 top,
275 width,
276 height,
277 state,
278 gpuSettings.context_type,
279 ((gpuSettings.flags & GHOST_gpuStereoVisual) != 0),
280 (GHOST_WindowWin32 *)parentWindow,
281 ((gpuSettings.flags & GHOST_gpuDebugContext) != 0),
282 is_dialog,
283 gpuSettings.preferred_device);
284
285 if (window->getValid()) {
286 /* Store the pointer to the window */
287 m_windowManager->addWindow(window);
288 m_windowManager->setActiveWindow(window);
289 }
290 else {
291 GHOST_PRINT("GHOST_SystemWin32::createWindow(): window invalid\n");
292 delete window;
293 window = nullptr;
294 }
295
296 return window;
297}
298
305{
306 const bool debug_context = (gpuSettings.flags & GHOST_gpuDebugContext) != 0;
307
308 switch (gpuSettings.context_type) {
309#ifdef WITH_VULKAN_BACKEND
310 case GHOST_kDrawingContextTypeVulkan: {
311 GHOST_Context *context = new GHOST_ContextVK(
312 false, (HWND)0, 1, 2, debug_context, gpuSettings.preferred_device);
313 if (context->initializeDrawingContext()) {
314 return context;
315 }
316 delete context;
317 return nullptr;
318 }
319#endif
320
321#ifdef WITH_OPENGL_BACKEND
322 case GHOST_kDrawingContextTypeOpenGL: {
323
324 /* OpenGL needs a dummy window to create a context on windows. */
325 HWND wnd = CreateWindowA("STATIC",
326 "BlenderGLEW",
327 WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
328 0,
329 0,
330 64,
331 64,
332 nullptr,
333 nullptr,
334 GetModuleHandle(nullptr),
335 nullptr);
336
337 HDC mHDC = GetDC(wnd);
338 HDC prev_hdc = wglGetCurrentDC();
339 HGLRC prev_context = wglGetCurrentContext();
340
341 for (int minor = 6; minor >= 3; --minor) {
342 GHOST_Context *context = new GHOST_ContextWGL(
343 false,
344 true,
345 wnd,
346 mHDC,
347 WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
348 4,
349 minor,
350 (debug_context ? WGL_CONTEXT_DEBUG_BIT_ARB : 0),
352
353 if (context->initializeDrawingContext()) {
354 wglMakeCurrent(prev_hdc, prev_context);
355 return context;
356 }
357 delete context;
358 }
359 wglMakeCurrent(prev_hdc, prev_context);
360 return nullptr;
361 }
362#endif
363 default:
364 /* Unsupported backend. */
365 return nullptr;
366 }
367}
368
375{
376 delete context;
377
378 return GHOST_kSuccess;
379}
380
387{
388 HWND wnd = CreateWindowA("STATIC",
389 "Blender XR",
390 WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
391 0,
392 0,
393 64,
394 64,
395 nullptr,
396 nullptr,
397 GetModuleHandle(nullptr),
398 nullptr);
399
400 GHOST_ContextD3D *context = new GHOST_ContextD3D(false, wnd);
401 if (context->initializeDrawingContext()) {
402 return context;
403 }
404 delete context;
405 return nullptr;
406}
407
409{
410 delete context;
411
412 return GHOST_kSuccess;
413}
414
415bool GHOST_SystemWin32::processEvents(bool waitForEvent)
416{
417 MSG msg;
418 bool hasEventHandled = false;
419
420 do {
422
423 if (waitForEvent && !::PeekMessage(&msg, nullptr, 0, 0, PM_NOREMOVE)) {
424#if 1
425 ::Sleep(1);
426#else
427 uint64_t next = timerMgr->nextFireTime();
428 int64_t maxSleep = next - getMilliSeconds();
429
430 if (next == GHOST_kFireTimeNever) {
431 ::WaitMessage();
432 }
433 else if (maxSleep >= 0.0) {
434 ::SetTimer(nullptr, 0, maxSleep, nullptr);
435 ::WaitMessage();
436 ::KillTimer(nullptr, 0);
437 }
438#endif
439 }
440
441 if (timerMgr->fireTimers(getMilliSeconds())) {
442 hasEventHandled = true;
443 }
444
446
447 /* Process all the events waiting for us. */
448 while (::PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE) != 0) {
449 /* #TranslateMessage doesn't alter the message, and doesn't change our raw keyboard data.
450 * Needed for #MapVirtualKey or if we ever need to get chars from wm_ime_char or similar. */
451 ::TranslateMessage(&msg);
452 ::DispatchMessageW(&msg);
453 hasEventHandled = true;
454 }
455
457
458 /* `PeekMessage` above is allowed to dispatch messages to the `wndproc` without us
459 * noticing, so we need to check the event manager here to see if there are
460 * events waiting in the queue. */
461 hasEventHandled |= this->m_eventManager->getNumEvents() > 0;
462
463 } while (waitForEvent && !hasEventHandled);
464
465 return hasEventHandled;
466}
467
469{
470 POINT point;
471 if (::GetCursorPos(&point)) {
472 x = point.x;
473 y = point.y;
474 return GHOST_kSuccess;
475 }
476 return GHOST_kFailure;
477}
478
480{
481 if (!::GetActiveWindow()) {
482 return GHOST_kFailure;
483 }
484 return ::SetCursorPos(x, y) == TRUE ? GHOST_kSuccess : GHOST_kFailure;
485}
486
488{
489 POINT point;
490 if (!GetCursorPos(&point)) {
491 return GHOST_kFailure;
492 }
493
494 HDC dc = GetDC(NULL);
495 if (dc == NULL) {
496 return GHOST_kFailure;
497 }
498
499 COLORREF color = GetPixel(dc, point.x, point.y);
500 ReleaseDC(NULL, dc);
501
502 if (color == CLR_INVALID) {
503 return GHOST_kFailure;
504 }
505
506 r_color[0] = GetRValue(color) / 255.0f;
507 r_color[1] = GetGValue(color) / 255.0f;
508 r_color[2] = GetBValue(color) / 255.0f;
509 return GHOST_kSuccess;
510}
511
513{
514 /* Get cursor position from the OS. Do not use the supplied positions as those
515 * could be incorrect, especially if using multiple windows of differing OS scale. */
516 POINT point;
517 if (!GetCursorPos(&point)) {
518 return nullptr;
519 }
520
521 HWND win = WindowFromPoint(point);
522 if (win == NULL) {
523 return nullptr;
524 }
525
526 return m_windowManager->getWindowAssociatedWithOSWindow((const void *)win);
527}
528
530{
531 /* `GetAsyncKeyState` returns the current interrupt-level state of the hardware, which is needed
532 * when passing key states to a newly-activated window - #40059. Alternative `GetKeyState` only
533 * returns the state as processed by the thread's message queue. */
534 bool down = HIBYTE(::GetAsyncKeyState(VK_LSHIFT)) != 0;
536 down = HIBYTE(::GetAsyncKeyState(VK_RSHIFT)) != 0;
538
539 down = HIBYTE(::GetAsyncKeyState(VK_LMENU)) != 0;
540 keys.set(GHOST_kModifierKeyLeftAlt, down);
541 down = HIBYTE(::GetAsyncKeyState(VK_RMENU)) != 0;
543
544 down = HIBYTE(::GetAsyncKeyState(VK_LCONTROL)) != 0;
546 down = HIBYTE(::GetAsyncKeyState(VK_RCONTROL)) != 0;
548
549 down = HIBYTE(::GetAsyncKeyState(VK_LWIN)) != 0;
550 keys.set(GHOST_kModifierKeyLeftOS, down);
551 down = HIBYTE(::GetAsyncKeyState(VK_RWIN)) != 0;
552 keys.set(GHOST_kModifierKeyRightOS, down);
553
554 return GHOST_kSuccess;
555}
556
558{
559 /* Check for swapped buttons (left-handed mouse buttons)
560 * GetAsyncKeyState() will give back the state of the physical mouse buttons.
561 */
562 bool swapped = ::GetSystemMetrics(SM_SWAPBUTTON) == TRUE;
563
564 bool down = HIBYTE(::GetAsyncKeyState(VK_LBUTTON)) != 0;
565 buttons.set(swapped ? GHOST_kButtonMaskRight : GHOST_kButtonMaskLeft, down);
566
567 down = HIBYTE(::GetAsyncKeyState(VK_MBUTTON)) != 0;
568 buttons.set(GHOST_kButtonMaskMiddle, down);
569
570 down = HIBYTE(::GetAsyncKeyState(VK_RBUTTON)) != 0;
571 buttons.set(swapped ? GHOST_kButtonMaskLeft : GHOST_kButtonMaskRight, down);
572 return GHOST_kSuccess;
573}
574
576{
579 ~(
580 /* WIN32 has no support for a primary selection clipboard. */
582 /* WIN32 doesn't define a Hyper modifier key,
583 * it's possible another modifier could be optionally used in it's place. */
585}
586
588{
590 InitCommonControls();
591
592 /* Disable scaling on high DPI displays on Vista */
593 SetProcessDPIAware();
594 initRawInput();
595
596 /* Determine whether this system has a high frequency performance counter. */
597 m_hasPerformanceCounter = ::QueryPerformanceFrequency((LARGE_INTEGER *)&m_freq) == TRUE;
598
599 if (success) {
600 WNDCLASSW wc = {0};
601 wc.style = CS_HREDRAW | CS_VREDRAW;
602 wc.lpfnWndProc = s_wndProc;
603 wc.cbClsExtra = 0;
604 wc.cbWndExtra = 0;
605 wc.hInstance = ::GetModuleHandle(0);
606 wc.hIcon = ::LoadIcon(wc.hInstance, "APPICON");
607
608 if (!wc.hIcon) {
609 ::LoadIcon(nullptr, IDI_APPLICATION);
610 }
611 wc.hCursor = ::LoadCursor(0, IDC_ARROW);
612 wc.hbrBackground = (HBRUSH)GetStockObject(DKGRAY_BRUSH);
613 wc.lpszMenuName = 0;
614 wc.lpszClassName = L"GHOST_WindowClass";
615
616 /* Use #RegisterClassEx for setting small icon. */
617 if (::RegisterClassW(&wc) == 0) {
618 success = GHOST_kFailure;
619 }
620 }
621
622 return success;
623}
624
629
630GHOST_TKey GHOST_SystemWin32::hardKey(RAWINPUT const &raw, bool *r_key_down)
631{
632 /* #RI_KEY_BREAK doesn't work for sticky keys release, so we also check for the up message. */
633 uint msg = raw.data.keyboard.Message;
634 *r_key_down = !(raw.data.keyboard.Flags & RI_KEY_BREAK) && msg != WM_KEYUP && msg != WM_SYSKEYUP;
635
636 return this->convertKey(raw.data.keyboard.VKey,
637 raw.data.keyboard.MakeCode,
638 (raw.data.keyboard.Flags & (RI_KEY_E1 | RI_KEY_E0)));
639}
640
647GHOST_TKey GHOST_SystemWin32::processSpecialKey(short vKey, short /*scanCode*/) const
648{
650 if (vKey == 0xFF) {
651 /* 0xFF is not a valid virtual key code. */
652 return key;
653 }
654
655 char ch = char(MapVirtualKeyA(vKey, MAPVK_VK_TO_CHAR));
656 switch (ch) {
657 case u'\"':
658 case u'\'':
659 key = GHOST_kKeyQuote;
660 break;
661 case u'.':
663 break;
664 case u'/':
665 key = GHOST_kKeySlash;
666 break;
667 case u'`':
668 case u'²':
670 break;
671 case u'i':
672 /* `i` key on Turkish keyboard. */
673 key = GHOST_kKeyI;
674 break;
675 default:
676 if (vKey == VK_OEM_7) {
677 key = GHOST_kKeyQuote;
678 }
679 else if (vKey == VK_OEM_8) {
680 if (PRIMARYLANGID(m_langId) == LANG_FRENCH) {
681 /* OEM key; used purely for shortcuts. */
682 key = GHOST_kKeyF13;
683 }
684 }
685 break;
686 }
687
688 return key;
689}
690
691GHOST_TKey GHOST_SystemWin32::convertKey(short vKey, short scanCode, short extend) const
692{
693 GHOST_TKey key;
694
695 if ((vKey >= '0') && (vKey <= '9')) {
696 /* VK_0 thru VK_9 are the same as ASCII '0' thru '9' (0x30 - 0x39). */
697 key = (GHOST_TKey)(vKey - '0' + GHOST_kKey0);
698 }
699 else if ((vKey >= 'A') && (vKey <= 'Z')) {
700 /* VK_A thru VK_Z are the same as ASCII 'A' thru 'Z' (0x41 - 0x5A). */
701 key = (GHOST_TKey)(vKey - 'A' + GHOST_kKeyA);
702 }
703 else if ((vKey >= VK_F1) && (vKey <= VK_F24)) {
704 key = (GHOST_TKey)(vKey - VK_F1 + GHOST_kKeyF1);
705 }
706 else {
707 switch (vKey) {
708 case VK_RETURN:
709 key = (extend) ? GHOST_kKeyNumpadEnter : GHOST_kKeyEnter;
710 break;
711
712 case VK_BACK:
714 break;
715 case VK_TAB:
716 key = GHOST_kKeyTab;
717 break;
718 case VK_ESCAPE:
719 key = GHOST_kKeyEsc;
720 break;
721 case VK_SPACE:
722 key = GHOST_kKeySpace;
723 break;
724
725 case VK_INSERT:
726 case VK_NUMPAD0:
727 key = (extend) ? GHOST_kKeyInsert : GHOST_kKeyNumpad0;
728 break;
729 case VK_END:
730 case VK_NUMPAD1:
731 key = (extend) ? GHOST_kKeyEnd : GHOST_kKeyNumpad1;
732 break;
733 case VK_DOWN:
734 case VK_NUMPAD2:
735 key = (extend) ? GHOST_kKeyDownArrow : GHOST_kKeyNumpad2;
736 break;
737 case VK_NEXT:
738 case VK_NUMPAD3:
739 key = (extend) ? GHOST_kKeyDownPage : GHOST_kKeyNumpad3;
740 break;
741 case VK_LEFT:
742 case VK_NUMPAD4:
743 key = (extend) ? GHOST_kKeyLeftArrow : GHOST_kKeyNumpad4;
744 break;
745 case VK_CLEAR:
746 case VK_NUMPAD5:
747 key = (extend) ? GHOST_kKeyUnknown : GHOST_kKeyNumpad5;
748 break;
749 case VK_RIGHT:
750 case VK_NUMPAD6:
751 key = (extend) ? GHOST_kKeyRightArrow : GHOST_kKeyNumpad6;
752 break;
753 case VK_HOME:
754 case VK_NUMPAD7:
755 key = (extend) ? GHOST_kKeyHome : GHOST_kKeyNumpad7;
756 break;
757 case VK_UP:
758 case VK_NUMPAD8:
759 key = (extend) ? GHOST_kKeyUpArrow : GHOST_kKeyNumpad8;
760 break;
761 case VK_PRIOR:
762 case VK_NUMPAD9:
763 key = (extend) ? GHOST_kKeyUpPage : GHOST_kKeyNumpad9;
764 break;
765 case VK_DECIMAL:
766 case VK_DELETE:
767 key = (extend) ? GHOST_kKeyDelete : GHOST_kKeyNumpadPeriod;
768 break;
769
770 case VK_SNAPSHOT:
772 break;
773 case VK_PAUSE:
774 key = GHOST_kKeyPause;
775 break;
776 case VK_MULTIPLY:
778 break;
779 case VK_SUBTRACT:
781 break;
782 case VK_DIVIDE:
784 break;
785 case VK_ADD:
787 break;
788
789 case VK_SEMICOLON:
791 break;
792 case VK_EQUALS:
793 key = GHOST_kKeyEqual;
794 break;
795 case VK_COMMA:
796 key = GHOST_kKeyComma;
797 break;
798 case VK_MINUS:
799 key = GHOST_kKeyMinus;
800 break;
801 case VK_PERIOD:
802 key = GHOST_kKeyPeriod;
803 break;
804 case VK_SLASH:
805 key = GHOST_kKeySlash;
806 break;
807 case VK_BACK_QUOTE:
809 break;
810 case VK_OPEN_BRACKET:
812 break;
813 case VK_BACK_SLASH:
815 break;
816 case VK_CLOSE_BRACKET:
818 break;
819 case VK_GR_LESS:
820 key = GHOST_kKeyGrLess;
821 break;
822
823 case VK_SHIFT:
824 /* Check single shift presses */
825 if (scanCode == 0x36) {
827 }
828 else if (scanCode == 0x2a) {
830 }
831 else {
832 /* Must be a combination SHIFT (Left or Right) + a Key
833 * Ignore this as the next message will contain
834 * the desired "Key" */
835 key = GHOST_kKeyUnknown;
836 }
837 break;
838 case VK_CONTROL:
840 break;
841 case VK_MENU:
842 key = (extend) ? GHOST_kKeyRightAlt : GHOST_kKeyLeftAlt;
843 break;
844 case VK_LWIN:
845 key = GHOST_kKeyLeftOS;
846 break;
847 case VK_RWIN:
848 key = GHOST_kKeyRightOS;
849 break;
850 case VK_APPS:
851 key = GHOST_kKeyApp;
852 break;
853 case VK_NUMLOCK:
854 key = GHOST_kKeyNumLock;
855 break;
856 case VK_SCROLL:
858 break;
859 case VK_CAPITAL:
860 key = GHOST_kKeyCapsLock;
861 break;
862 case VK_MEDIA_PLAY_PAUSE:
864 break;
865 case VK_MEDIA_STOP:
867 break;
868 case VK_MEDIA_PREV_TRACK:
870 break;
871 case VK_MEDIA_NEXT_TRACK:
873 break;
874 case VK_OEM_7:
875 case VK_OEM_8:
876 default:
877 key = ((GHOST_SystemWin32 *)getSystem())->processSpecialKey(vKey, scanCode);
878 break;
879 }
880 }
881
882 return key;
883}
884
886 GHOST_WindowWin32 *window,
888{
890
891 GHOST_TabletData td = window->getTabletData();
892 const uint64_t event_ms = getMessageTime(system);
893
894 /* Move mouse to button event position. */
895 if (window->getTabletData().Active != GHOST_kTabletModeNone) {
896 /* Tablet should be handling in between mouse moves, only move to event position. */
897 DWORD msgPos = ::GetMessagePos();
898 int msgPosX = GET_X_LPARAM(msgPos);
899 int msgPosY = GET_Y_LPARAM(msgPos);
900 system->pushEvent(
901 new GHOST_EventCursor(event_ms, GHOST_kEventCursorMove, window, msgPosX, msgPosY, td));
902
903 if (type == GHOST_kEventButtonDown) {
904 WINTAB_PRINTF("HWND %p OS button down\n", window->getHWND());
905 }
906 else if (type == GHOST_kEventButtonUp) {
907 WINTAB_PRINTF("HWND %p OS button up\n", window->getHWND());
908 }
909 }
910
912 return new GHOST_EventButton(event_ms, type, window, mask, td);
913}
914
916{
917 GHOST_Wintab *wt = window->getWintab();
918 if (!wt) {
919 return;
920 }
921
923
924 std::vector<GHOST_WintabInfoWin32> wintabInfo;
925 wt->getInput(wintabInfo);
926
927 /* Wintab provided coordinates are untrusted until a Wintab and Win32 button down event match.
928 * This is checked on every button down event, and revoked if there is a mismatch. This can
929 * happen when Wintab incorrectly scales cursor position or is in mouse mode.
930 *
931 * If Wintab was never trusted while processing this Win32 event, a fallback Ghost cursor move
932 * event is created at the position of the Win32 WT_PACKET event. */
933 bool mouseMoveHandled;
934 bool useWintabPos;
935 mouseMoveHandled = useWintabPos = wt->trustCoordinates();
936
937 for (GHOST_WintabInfoWin32 &info : wintabInfo) {
938 switch (info.type) {
940 if (!useWintabPos) {
941 continue;
942 }
943
944 wt->mapWintabToSysCoordinates(info.x, info.y, info.x, info.y);
945 system->pushEvent(new GHOST_EventCursor(
946 info.time, GHOST_kEventCursorMove, window, info.x, info.y, info.tabletData));
947
948 break;
949 }
951 WINTAB_PRINTF("HWND %p Wintab button down", window->getHWND());
952
953 uint message;
954 switch (info.button) {
956 message = WM_LBUTTONDOWN;
957 break;
959 message = WM_RBUTTONDOWN;
960 break;
962 message = WM_MBUTTONDOWN;
963 break;
964 default:
965 continue;
966 }
967
968 /* Wintab buttons are modal, but the API does not inform us what mode a pressed button is
969 * in. Only issue button events if we can steal an equivalent Win32 button event from the
970 * event queue. */
971 MSG msg;
972 if (PeekMessage(&msg, window->getHWND(), message, message, PM_NOYIELD) &&
973 msg.message != WM_QUIT)
974 {
975
976 /* Test for Win32/Wintab button down match. */
977 useWintabPos = wt->testCoordinates(msg.pt.x, msg.pt.y, info.x, info.y);
978 if (!useWintabPos) {
979 WINTAB_PRINTF(" ... but associated system button mismatched position\n");
980 continue;
981 }
982
983 WINTAB_PRINTF(" ... associated to system button\n");
984
985 /* Steal the Win32 event which was previously peeked. */
986 PeekMessage(&msg, window->getHWND(), message, message, PM_REMOVE | PM_NOYIELD);
987
988 /* Move cursor to button location, to prevent incorrect cursor position when
989 * transitioning from unsynchronized Win32 to Wintab cursor control. */
990 wt->mapWintabToSysCoordinates(info.x, info.y, info.x, info.y);
991 system->pushEvent(new GHOST_EventCursor(
992 info.time, GHOST_kEventCursorMove, window, info.x, info.y, info.tabletData));
993
995 system->pushEvent(
996 new GHOST_EventButton(info.time, info.type, window, info.button, info.tabletData));
997
998 mouseMoveHandled = true;
999 }
1000 else {
1001 WINTAB_PRINTF(" ... but no system button\n");
1002 }
1003 break;
1004 }
1005 case GHOST_kEventButtonUp: {
1006 WINTAB_PRINTF("HWND %p Wintab button up", window->getHWND());
1007 if (!useWintabPos) {
1008 WINTAB_PRINTF(" ... but Wintab position isn't trusted\n");
1009 continue;
1010 }
1011
1012 uint message;
1013 switch (info.button) {
1015 message = WM_LBUTTONUP;
1016 break;
1018 message = WM_RBUTTONUP;
1019 break;
1021 message = WM_MBUTTONUP;
1022 break;
1023 default:
1024 continue;
1025 }
1026
1027 /* Wintab buttons are modal, but the API does not inform us what mode a pressed button is
1028 * in. Only issue button events if we can steal an equivalent Win32 button event from the
1029 * event queue. */
1030 MSG msg;
1031 if (PeekMessage(&msg, window->getHWND(), message, message, PM_REMOVE | PM_NOYIELD) &&
1032 msg.message != WM_QUIT)
1033 {
1034
1035 WINTAB_PRINTF(" ... associated to system button\n");
1037 system->pushEvent(
1038 new GHOST_EventButton(info.time, info.type, window, info.button, info.tabletData));
1039 }
1040 else {
1041 WINTAB_PRINTF(" ... but no system button\n");
1042 }
1043 break;
1044 }
1045 default:
1046 break;
1047 }
1048 }
1049
1050 /* Fallback cursor movement if Wintab position were never trusted while processing this event. */
1051 if (!mouseMoveHandled) {
1052 DWORD pos = GetMessagePos();
1053 int x = GET_X_LPARAM(pos);
1054 int y = GET_Y_LPARAM(pos);
1056
1057 system->pushEvent(
1058 new GHOST_EventCursor(getMessageTime(system), GHOST_kEventCursorMove, window, x, y, td));
1059 }
1060}
1061
1063 uint type, GHOST_WindowWin32 *window, WPARAM wParam, LPARAM lParam, bool &eventHandled)
1064{
1065 /* Pointer events might fire when changing windows for a device which is set to use Wintab,
1066 * even when Wintab is left enabled but set to the bottom of Wintab overlap order. */
1068 return;
1069 }
1070
1072 std::vector<GHOST_PointerInfoWin32> pointerInfo;
1073
1074 if (window->getPointerInfo(pointerInfo, wParam, lParam) != GHOST_kSuccess) {
1075 return;
1076 }
1077
1078 switch (type) {
1079 case WM_POINTERUPDATE: {
1080 /* Coalesced pointer events are reverse chronological order, reorder chronologically.
1081 * Only contiguous move events are coalesced. */
1082 for (uint32_t i = pointerInfo.size(); i-- > 0;) {
1083 system->pushEvent(new GHOST_EventCursor(pointerInfo[i].time,
1085 window,
1086 pointerInfo[i].pixelLocation.x,
1087 pointerInfo[i].pixelLocation.y,
1088 pointerInfo[i].tabletData));
1089 }
1090
1091 /* Leave event unhandled so that system cursor is moved. */
1092
1093 break;
1094 }
1095 case WM_POINTERDOWN: {
1096 /* Move cursor to point of contact because GHOST_EventButton does not include position. */
1097 system->pushEvent(new GHOST_EventCursor(pointerInfo[0].time,
1099 window,
1100 pointerInfo[0].pixelLocation.x,
1101 pointerInfo[0].pixelLocation.y,
1102 pointerInfo[0].tabletData));
1103 system->pushEvent(new GHOST_EventButton(pointerInfo[0].time,
1105 window,
1106 pointerInfo[0].buttonMask,
1107 pointerInfo[0].tabletData));
1109
1110 /* Mark event handled so that mouse button events are not generated. */
1111 eventHandled = true;
1112
1113 break;
1114 }
1115 case WM_POINTERUP: {
1116 system->pushEvent(new GHOST_EventButton(pointerInfo[0].time,
1118 window,
1119 pointerInfo[0].buttonMask,
1120 pointerInfo[0].tabletData));
1122
1123 /* Mark event handled so that mouse button events are not generated. */
1124 eventHandled = true;
1125
1126 break;
1127 }
1128 default: {
1129 break;
1130 }
1131 }
1132}
1133
1135 const int32_t screen_co[2])
1136{
1138
1139 if (window->getTabletData().Active != GHOST_kTabletModeNone) {
1140 /* While pen devices are in range, cursor movement is handled by tablet input processing. */
1141 return nullptr;
1142 }
1143
1144 int32_t x_screen = screen_co[0], y_screen = screen_co[1];
1145 if (window->getCursorGrabModeIsWarp()) {
1146 /* WORKAROUND:
1147 * Sometimes Windows ignores `SetCursorPos()` or `SendInput()` calls or the mouse event is
1148 * outdated. Identify these cases by checking if the cursor is not yet within bounds. */
1149 static bool is_warping_x = false;
1150 static bool is_warping_y = false;
1151
1152 int32_t x_new = x_screen;
1153 int32_t y_new = y_screen;
1154 int32_t x_accum, y_accum;
1155
1156 /* Warp within bounds. */
1157 {
1159 if (window->getCursorGrabBounds(bounds) == GHOST_kFailure) {
1160 /* Use custom grab bounds if available, window bounds if not. */
1161 window->getClientBounds(bounds);
1162 }
1163
1164 /* WARNING(@ideasman42): The current warping logic fails to warp on every event,
1165 * so the box needs to small enough not to let the cursor escape the window but large
1166 * enough that the cursor isn't being warped every time. If this was not the case it
1167 * would be less trouble to simply warp the cursor to the center of the screen on
1168 * every motion, see: D16558 (alternative fix for #102346). */
1169
1170 /* Rather than adjust the bounds, use a margin based on the bounds width. */
1171 int32_t bounds_margin = (window->getCursorGrabMode() == GHOST_kGrabHide) ?
1172 bounds.getWidth() / 10 :
1173 2;
1174 GHOST_TAxisFlag bounds_axis = window->getCursorGrabAxis();
1175
1176 /* Could also clamp to screen bounds wrap with a window outside the view will
1177 * fail at the moment. Use inset in case the window is at screen bounds. */
1178 bounds.wrapPoint(x_new, y_new, bounds_margin, bounds_axis);
1179 }
1180
1181 window->getCursorGrabAccum(x_accum, y_accum);
1182 if (x_new != x_screen || y_new != y_screen) {
1183 system->setCursorPosition(x_new, y_new); /* wrap */
1184
1185 /* Do not update the accum values if we are an outdated or failed pos-warp event. */
1186 if (!is_warping_x) {
1187 is_warping_x = x_new != x_screen;
1188 if (is_warping_x) {
1189 x_accum += (x_screen - x_new);
1190 }
1191 }
1192
1193 if (!is_warping_y) {
1194 is_warping_y = y_new != y_screen;
1195 if (is_warping_y) {
1196 y_accum += (y_screen - y_new);
1197 }
1198 }
1199 window->setCursorGrabAccum(x_accum, y_accum);
1200
1201 /* When wrapping we don't need to add an event because the setCursorPosition call will cause
1202 * a new event after. */
1203 return nullptr;
1204 }
1205
1206 is_warping_x = false;
1207 is_warping_y = false;
1208 x_screen += x_accum;
1209 y_screen += y_accum;
1210 }
1211
1212 return new GHOST_EventCursor(getMessageTime(system),
1214 window,
1215 x_screen,
1216 y_screen,
1218}
1219
1221 WPARAM wParam,
1222 LPARAM /*lParam*/)
1223{
1225
1226 int acc = system->m_wheelDeltaAccumVertical;
1227 int delta = GET_WHEEL_DELTA_WPARAM(wParam);
1228
1229 if (acc * delta < 0) {
1230 /* Scroll direction reversed. */
1231 acc = 0;
1232 }
1233 acc += delta;
1234 int direction = (acc >= 0) ? 1 : -1;
1235 acc = abs(acc);
1236
1237 while (acc >= WHEEL_DELTA) {
1238 system->pushEvent(new GHOST_EventWheel(
1239 getMessageTime(system), window, GHOST_kEventWheelAxisVertical, direction));
1240 acc -= WHEEL_DELTA;
1241 }
1242 system->m_wheelDeltaAccumVertical = acc * direction;
1243}
1244
1247 WPARAM wParam,
1248 LPARAM /*lParam*/)
1249{
1251
1252 int acc = system->m_wheelDeltaAccumHorizontal;
1253 int delta = GET_WHEEL_DELTA_WPARAM(wParam);
1254
1255 if (acc * delta < 0) {
1256 /* Scroll direction reversed. */
1257 acc = 0;
1258 }
1259 acc += delta;
1260 int direction = (acc >= 0) ? 1 : -1;
1261 acc = abs(acc);
1262
1263 while (acc >= WHEEL_DELTA) {
1264 system->pushEvent(new GHOST_EventWheel(
1265 getMessageTime(system), window, GHOST_kEventWheelAxisHorizontal, direction));
1266 acc -= WHEEL_DELTA;
1267 }
1268 system->m_wheelDeltaAccumHorizontal = acc * direction;
1269}
1270
1272{
1273 const char vk = raw.data.keyboard.VKey;
1274 bool key_down = false;
1276 GHOST_TKey key = system->hardKey(raw, &key_down);
1277 GHOST_EventKey *event;
1278
1279 /* Scan code (device-dependent identifier for the key on the keyboard) for the Alt key.
1280 * https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#scan-codes */
1281 constexpr USHORT ALTGR_MAKE_CODE = 0x38;
1282
1283 /* If the keyboard layout includes AltGr and the virtual key is Control, yet the
1284 * scan-code is actually for Right Alt (ALTGR_MAKE_CODE scan code with E0 prefix).
1285 * Ignore these, so treating AltGR as regular Alt. #68256 */
1286 if (system->m_hasAltGr && vk == VK_CONTROL && raw.data.keyboard.MakeCode == ALTGR_MAKE_CODE &&
1287 (raw.data.keyboard.Flags & RI_KEY_E0))
1288 {
1289 return nullptr;
1290 }
1291
1292 /* NOTE(@ideasman42): key repeat in WIN32 also applies to modifier-keys.
1293 * Check for this case and filter out modifier-repeat.
1294 * Typically keyboard events are *not* filtered as part of GHOST's event handling.
1295 * As other GHOST back-ends don't have the behavior, it's simplest not to send them through.
1296 * Ideally it would be possible to check the key-map for keys that repeat but this doesn't look
1297 * to be supported. */
1298 bool is_repeat = false;
1299 bool is_repeated_modifier = false;
1300 if (key_down) {
1301 if (HIBYTE(::GetKeyState(vk)) != 0) {
1302 /* This thread's message queue shows this key as already down. */
1303 is_repeat = true;
1304 is_repeated_modifier = GHOST_KEY_MODIFIER_CHECK(key);
1305 }
1306 }
1307
1308 /* We used to check `if (key != GHOST_kKeyUnknown)`, but since the message
1309 * values `WM_SYSKEYUP`, `WM_KEYUP` and `WM_CHAR` are ignored, we capture
1310 * those events here as well. */
1311 if (!is_repeated_modifier) {
1312 char utf8_char[6] = {0};
1313 BYTE state[256];
1314 const BOOL has_state = GetKeyboardState((PBYTE)state);
1315 const bool ctrl_pressed = has_state && state[VK_CONTROL] & 0x80;
1316 const bool alt_pressed = has_state && state[VK_MENU] & 0x80;
1317 const bool win_pressed = has_state && (state[VK_LWIN] | state[VK_RWIN]) & 0x80;
1318
1319 /* We can be here with !key_down if processing dead keys (diacritics). See #103119. */
1320
1321 /* No text with control key pressed (Alt can be used to insert special characters though!). */
1322 if (ctrl_pressed && !alt_pressed) {
1323 /* Pass. */
1324 }
1325 else if (win_pressed) {
1326 /* Pass. No text if either Win key is pressed. #79702. */
1327 }
1328 /* Don't call #ToUnicodeEx on dead keys as it clears the buffer and so won't allow diacritical
1329 * composition. XXX: we are not checking return of #MapVirtualKeyW for high bit set, which is
1330 * what is supposed to indicate dead keys. But this is working now so approach cautiously. */
1331 else if (MapVirtualKeyW(vk, MAPVK_VK_TO_CHAR) != 0) {
1332 wchar_t utf16[3] = {0};
1333 int r;
1334 /* TODO: #ToUnicodeEx can respond with up to 4 UTF16 chars (only 2 here).
1335 * Could be up to 24 UTF8 bytes. */
1336 if ((r = ToUnicodeEx(
1337 vk, raw.data.keyboard.MakeCode, state, utf16, 2, 0, system->m_keylayout)))
1338 {
1339 if ((r > 0 && r < 3)) {
1340 utf16[r] = 0;
1341 conv_utf_16_to_8(utf16, utf8_char, 6);
1342 }
1343 else if (r == -1) {
1344 utf8_char[0] = '\0';
1345 }
1346 }
1347 if (!key_down) {
1348 /* Clear or wm_event_add_ghostevent will warn of unexpected data on key up. */
1349 utf8_char[0] = '\0';
1350 }
1351 }
1352
1353#ifdef WITH_INPUT_IME
1354 if (key_down && ((utf8_char[0] & 0x80) == 0)) {
1355 const char ascii = utf8_char[0];
1356 if (window->getImeInput()->IsImeKeyEvent(ascii, key)) {
1357 return nullptr;
1358 }
1359 }
1360#endif /* WITH_INPUT_IME */
1361
1362 event = new GHOST_EventKey(getMessageTime(system),
1364 window,
1365 key,
1366 is_repeat,
1367 utf8_char);
1368
1369#if 0 /* we already get this info via EventPrinter. */
1370 GHOST_PRINTF("%c\n", ascii);
1371#endif
1372 }
1373 else {
1374 event = nullptr;
1375 }
1376
1377 return event;
1378}
1379
1381{
1383 GHOST_Event *sizeEvent = new GHOST_Event(getMessageTime(system), GHOST_kEventWindowSize, window);
1384
1385 /* We get WM_SIZE before we fully init. Do not dispatch before we are continuously resizing. */
1386 if (window->m_inLiveResize) {
1387 system->pushEvent(sizeEvent);
1388 system->dispatchEvents();
1389 return nullptr;
1390 }
1391 return sizeEvent;
1392}
1393
1395 GHOST_WindowWin32 *window)
1396{
1398
1399 if (type == GHOST_kEventWindowActivate) {
1400 system->getWindowManager()->setActiveWindow(window);
1401 }
1402 else if (type == GHOST_kEventWindowDeactivate) {
1403 system->getWindowManager()->setWindowInactive(window);
1404 }
1405
1406 return new GHOST_Event(getMessageTime(system), type, window);
1407}
1408
1409#ifdef WITH_INPUT_IME
1410GHOST_Event *GHOST_SystemWin32::processImeEvent(GHOST_TEventType type,
1411 GHOST_WindowWin32 *window,
1413{
1415 return new GHOST_EventIME(getMessageTime(system), type, window, data);
1416}
1417#endif
1418
1420 GHOST_TDragnDropTypes draggedObjectType,
1421 GHOST_WindowWin32 *window,
1422 int mouseX,
1423 int mouseY,
1424 void *data)
1425{
1427 return system->pushEvent(new GHOST_EventDragnDrop(
1428 getMessageTime(system), eventType, draggedObjectType, window, mouseX, mouseY, data));
1429}
1430
1432{
1434
1435 /* If API is set to WinPointer (Windows Ink), unload Wintab so that trouble drivers don't disable
1436 * Windows Ink. Load Wintab when API is Automatic because decision logic relies on knowing
1437 * whether a Wintab device is present. */
1438 const bool loadWintab = GHOST_kTabletWinPointer != api;
1440
1441 for (GHOST_IWindow *win : wm->getWindows()) {
1442 GHOST_WindowWin32 *windowWin32 = (GHOST_WindowWin32 *)win;
1443 if (loadWintab) {
1444 windowWin32->loadWintab(GHOST_kWindowStateMinimized != windowWin32->getState());
1445
1446 if (windowWin32->usingTabletAPI(GHOST_kTabletWintab)) {
1447 windowWin32->resetPointerPenInfo();
1448 }
1449 }
1450 else {
1451 windowWin32->closeWintab();
1452 }
1453 }
1454}
1455
1461
1463{
1464 minmax->ptMinTrackSize.x = 320;
1465 minmax->ptMinTrackSize.y = 240;
1466}
1467
1468#ifdef WITH_INPUT_NDOF
1469bool GHOST_SystemWin32::processNDOF(RAWINPUT const &raw)
1470{
1471 bool eventSent = false;
1472 uint64_t now = getMilliSeconds();
1473
1474 RID_DEVICE_INFO info;
1475 unsigned infoSize = sizeof(RID_DEVICE_INFO);
1476 info.cbSize = infoSize;
1477
1478 GetRawInputDeviceInfo(raw.header.hDevice, RIDI_DEVICEINFO, &info, &infoSize);
1479 /* Since there can be multiple NDOF devices connected, always set the current device. */
1480 if (info.dwType == RIM_TYPEHID) {
1481 m_ndofManager->setDevice(info.hid.dwVendorId, info.hid.dwProductId);
1482 }
1483 else {
1484 GHOST_PRINT("<!> not a HID device... mouse/kb perhaps?\n");
1485 }
1486
1487 /* The NDOF manager sends button changes immediately, and *pretends* to
1488 * send motion. Mark as 'sent' so motion will always get dispatched. */
1489 eventSent = true;
1490
1491 BYTE const *data = raw.data.hid.bRawData;
1492
1493 BYTE packetType = data[0];
1494 switch (packetType) {
1495 case 0x1: { /* Translation. */
1496 const short *axis = (short *)(data + 1);
1497 /* Massage into blender view coords (same goes for rotation). */
1498 const int t[3] = {axis[0], -axis[2], axis[1]};
1499 m_ndofManager->updateTranslation(t, now);
1500
1501 if (raw.data.hid.dwSizeHid == 13) {
1502 /* This report also includes rotation. */
1503 const int r[3] = {-axis[3], axis[5], -axis[4]};
1504 m_ndofManager->updateRotation(r, now);
1505
1506 /* I've never gotten one of these, has anyone else? */
1507 GHOST_PRINT("ndof: combined T + R\n");
1508 }
1509 break;
1510 }
1511 case 0x2: { /* Rotation. */
1512
1513 const short *axis = (short *)(data + 1);
1514 const int r[3] = {-axis[0], axis[2], -axis[1]};
1515 m_ndofManager->updateRotation(r, now);
1516 break;
1517 }
1518 case 0x3: { /* Buttons bitmask (older devices). */
1519 int button_bits;
1520 memcpy(&button_bits, data + 1, sizeof(button_bits));
1521 m_ndofManager->updateButtonsBitmask(button_bits, now);
1522 break;
1523 }
1524 case 0x1c: { /* Buttons numbers (newer devices). */
1525 NDOF_Button_Array buttons;
1526 const uint16_t *payload = reinterpret_cast<const uint16_t *>(data + 1);
1527 for (int i = 0; i < buttons.size(); i++) {
1528 buttons[i] = static_cast<GHOST_NDOF_ButtonT>(*(payload + i));
1529 }
1530 m_ndofManager->updateButtonsArray(buttons, now, NDOF_Button_Type::ShortButton);
1531 break;
1532 }
1533 case 0x1d: { /* Buttons (long press, newer devices). */
1534 NDOF_Button_Array buttons;
1535 const uint16_t *payload = reinterpret_cast<const uint16_t *>(data + 1);
1536 for (int i = 0; i < buttons.size(); i++) {
1537 buttons[i] = translateLongButtonToNDOFButton(*(payload + i));
1538 }
1539 m_ndofManager->updateButtonsArray(buttons, now, NDOF_Button_Type::LongButton);
1540 break;
1541 }
1542 }
1543 return eventSent;
1544}
1545#endif /* WITH_INPUT_NDOF */
1546
1548{
1549 GHOST_WindowWin32 *active_window = static_cast<GHOST_WindowWin32 *>(
1551 if (active_window) {
1552 active_window->updateDirectManipulation();
1553 }
1554}
1555
1557{
1558 GHOST_WindowWin32 *active_window = static_cast<GHOST_WindowWin32 *>(
1560
1561 if (!active_window) {
1562 return;
1563 }
1564
1565 GHOST_TTrackpadInfo trackpad_info = active_window->getTrackpadInfo();
1567
1568 int32_t cursor_x, cursor_y;
1569 system->getCursorPosition(cursor_x, cursor_y);
1570
1571 if (trackpad_info.x != 0 || trackpad_info.y != 0) {
1572 system->pushEvent(new GHOST_EventTrackpad(getMessageTime(system),
1573 active_window,
1575 cursor_x,
1576 cursor_y,
1577 trackpad_info.x,
1578 trackpad_info.y,
1579 trackpad_info.isScrollDirectionInverted));
1580 }
1581 if (trackpad_info.scale != 0) {
1582 system->pushEvent(new GHOST_EventTrackpad(getMessageTime(system),
1583 active_window,
1585 cursor_x,
1586 cursor_y,
1587 trackpad_info.scale,
1588 0,
1589 false));
1590 }
1591}
1592
1593LRESULT WINAPI GHOST_SystemWin32::s_wndProc(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam)
1594{
1595 GHOST_Event *event = nullptr;
1596 bool eventHandled = false;
1597
1598 LRESULT lResult = 0;
1600#ifdef WITH_INPUT_IME
1601 GHOST_EventManager *eventManager = system->getEventManager();
1602#endif
1603 GHOST_ASSERT(system, "GHOST_SystemWin32::s_wndProc(): system not initialized");
1604
1605 if (hwnd) {
1606
1607 if (msg == WM_NCCREATE) {
1608 /* Tell Windows to automatically handle scaling of non-client areas
1609 * such as the caption bar. #EnableNonClientDpiScaling was introduced in Windows 10. */
1610 HMODULE m_user32 = ::LoadLibrary("User32.dll");
1611 if (m_user32) {
1612 GHOST_WIN32_EnableNonClientDpiScaling fpEnableNonClientDpiScaling =
1613 (GHOST_WIN32_EnableNonClientDpiScaling)::GetProcAddress(m_user32,
1614 "EnableNonClientDpiScaling");
1615 if (fpEnableNonClientDpiScaling) {
1616 fpEnableNonClientDpiScaling(hwnd);
1617 }
1618 }
1619 }
1620
1621 GHOST_WindowWin32 *window = (GHOST_WindowWin32 *)::GetWindowLongPtr(hwnd, GWLP_USERDATA);
1622 if (window) {
1623 switch (msg) {
1624 /* We need to check if new key layout has AltGr. */
1625 case WM_INPUTLANGCHANGE: {
1626 system->handleKeyboardChange();
1627#ifdef WITH_INPUT_IME
1628 window->getImeInput()->UpdateInputLanguage();
1629 window->getImeInput()->UpdateConversionStatus(hwnd);
1630#endif
1631 break;
1632 }
1633 /* ==========================
1634 * Keyboard events, processed
1635 * ========================== */
1636 case WM_INPUT: {
1637 RAWINPUT raw;
1638 RAWINPUT *raw_ptr = &raw;
1639 uint rawSize = sizeof(RAWINPUT);
1640
1641 GetRawInputData((HRAWINPUT)lParam, RID_INPUT, raw_ptr, &rawSize, sizeof(RAWINPUTHEADER));
1642
1643 switch (raw.header.dwType) {
1644 case RIM_TYPEKEYBOARD: {
1645 event = processKeyEvent(window, raw);
1646 if (!event) {
1647 GHOST_PRINT("GHOST_SystemWin32::wndProc: key event ");
1648 GHOST_PRINT(msg);
1649 GHOST_PRINT(" key ignored\n");
1650 }
1651 break;
1652 }
1653#ifdef WITH_INPUT_NDOF
1654 case RIM_TYPEHID: {
1655 if (system->processNDOF(raw)) {
1656 eventHandled = true;
1657 }
1658 break;
1659 }
1660#endif
1661 }
1662 break;
1663 }
1664#ifdef WITH_INPUT_IME
1665 /* =================================================
1666 * IME events, processed, read more in `GHOST_IME.h`
1667 * ================================================= */
1668 case WM_IME_NOTIFY: {
1669 /* Update conversion status when IME is changed or input mode is changed. */
1670 if (wParam == IMN_SETOPENSTATUS || wParam == IMN_SETCONVERSIONMODE) {
1671 window->getImeInput()->UpdateConversionStatus(hwnd);
1672 }
1673 break;
1674 }
1675 case WM_IME_SETCONTEXT: {
1676 GHOST_ImeWin32 *ime = window->getImeInput();
1677 ime->UpdateInputLanguage();
1678 ime->UpdateConversionStatus(hwnd);
1679 ime->CreateImeWindow(hwnd);
1680 ime->CleanupComposition(hwnd);
1681 ime->CheckFirst(hwnd);
1682 break;
1683 }
1684 case WM_IME_STARTCOMPOSITION: {
1685 GHOST_ImeWin32 *ime = window->getImeInput();
1686 eventHandled = true;
1687 ime->CreateImeWindow(hwnd);
1688 ime->ResetComposition(hwnd);
1689 event = processImeEvent(GHOST_kEventImeCompositionStart, window, &ime->eventImeData);
1690 break;
1691 }
1692 case WM_IME_COMPOSITION: {
1693 GHOST_ImeWin32 *ime = window->getImeInput();
1694 eventHandled = true;
1695 ime->UpdateImeWindow(hwnd);
1696 ime->UpdateInfo(hwnd);
1697 if (ime->eventImeData.result.size()) {
1698 /* remove redundant IME event */
1699 eventManager->removeTypeEvents(GHOST_kEventImeComposition, window);
1700 }
1701 event = processImeEvent(GHOST_kEventImeComposition, window, &ime->eventImeData);
1702 break;
1703 }
1704 case WM_IME_ENDCOMPOSITION: {
1705 GHOST_ImeWin32 *ime = window->getImeInput();
1706 eventHandled = true;
1707 /* remove input event after end comp event, avoid redundant input */
1708 eventManager->removeTypeEvents(GHOST_kEventKeyDown, window);
1709 ime->ResetComposition(hwnd);
1710 ime->DestroyImeWindow(hwnd);
1711 event = processImeEvent(GHOST_kEventImeCompositionEnd, window, &ime->eventImeData);
1712 break;
1713 }
1714#endif /* WITH_INPUT_IME */
1715 /* ========================
1716 * Keyboard events, ignored
1717 * ======================== */
1718 case WM_KEYDOWN:
1719 case WM_SYSKEYDOWN:
1720 case WM_KEYUP:
1721 case WM_SYSKEYUP:
1722 /* These functions were replaced by #WM_INPUT. */
1723 case WM_CHAR:
1724 /* The #WM_CHAR message is posted to the window with the keyboard focus when
1725 * a WM_KEYDOWN message is translated by the #TranslateMessage function.
1726 * WM_CHAR contains the character code of the key that was pressed. */
1727 case WM_DEADCHAR:
1728 /* The #WM_DEADCHAR message is posted to the window with the keyboard focus when a
1729 * WM_KEYUP message is translated by the #TranslateMessage function. WM_DEADCHAR
1730 * specifies a character code generated by a dead key. A dead key is a key that
1731 * generates a character, such as the umlaut (double-dot), that is combined with
1732 * another character to form a composite character. For example, the umlaut-O
1733 * character (Ö) is generated by typing the dead key for the umlaut character, and
1734 * then typing the O key. */
1735 break;
1736 case WM_SYSDEADCHAR:
1737 /* The #WM_SYSDEADCHAR message is sent to the window with the keyboard focus when
1738 * a WM_SYSKEYDOWN message is translated by the #TranslateMessage function.
1739 * WM_SYSDEADCHAR specifies the character code of a system dead key - that is,
1740 * a dead key that is pressed while holding down the alt key. */
1741 case WM_SYSCHAR: {
1742 /* #The WM_SYSCHAR message is sent to the window with the keyboard focus when
1743 * a WM_SYSCHAR message is translated by the #TranslateMessage function.
1744 * WM_SYSCHAR specifies the character code of a dead key - that is,
1745 * a dead key that is pressed while holding down the alt key.
1746 * To prevent the sound, #DefWindowProc must be avoided by return. */
1747 break;
1748 }
1749 case WM_SYSCOMMAND: {
1750 /* The #WM_SYSCOMMAND message is sent to the window when system commands such as
1751 * maximize, minimize or close the window are triggered. Also it is sent when ALT
1752 * button is press for menu. To prevent this we must return preventing #DefWindowProc.
1753 *
1754 * Note that the four low-order bits of the wParam parameter are used internally by the
1755 * OS. To obtain the correct result when testing the value of wParam, an application must
1756 * combine the value 0xFFF0 with the wParam value by using the bit-wise AND operator. */
1757 switch (wParam & 0xFFF0) {
1758 case SC_KEYMENU: {
1759 eventHandled = true;
1760 break;
1761 }
1762 case SC_RESTORE: {
1763 ::ShowWindow(hwnd, SW_RESTORE);
1764 window->setState(window->getState());
1765
1766 GHOST_Wintab *wt = window->getWintab();
1767 if (wt) {
1768 wt->enable();
1769 }
1770
1771 eventHandled = true;
1772 break;
1773 }
1774 case SC_MAXIMIZE: {
1775 GHOST_Wintab *wt = window->getWintab();
1776 if (wt) {
1777 wt->enable();
1778 }
1779 /* Don't report event as handled so that default handling occurs. */
1780 break;
1781 }
1782 case SC_MINIMIZE: {
1783 GHOST_Wintab *wt = window->getWintab();
1784 if (wt) {
1785 wt->disable();
1786 }
1787 /* Don't report event as handled so that default handling occurs. */
1788 break;
1789 }
1790 }
1791 break;
1792 }
1793 /* ========================
1794 * Wintab events, processed
1795 * ======================== */
1796 case WT_CSRCHANGE: {
1797 WINTAB_PRINTF("HWND %p HCTX %p WT_CSRCHANGE\n", window->getHWND(), (void *)lParam);
1798 GHOST_Wintab *wt = window->getWintab();
1799 if (wt) {
1800 wt->updateCursorInfo();
1801 }
1802 eventHandled = true;
1803 break;
1804 }
1805 case WT_PROXIMITY: {
1806 WINTAB_PRINTF("HWND %p HCTX %p WT_PROXIMITY\n", window->getHWND(), (void *)wParam);
1807 if (LOWORD(lParam)) {
1808 WINTAB_PRINTF(" Cursor entering context.\n");
1809 }
1810 else {
1811 WINTAB_PRINTF(" Cursor leaving context.\n");
1812 }
1813 if (HIWORD(lParam)) {
1814 WINTAB_PRINTF(" Cursor entering or leaving hardware proximity.\n");
1815 }
1816 else {
1817 WINTAB_PRINTF(" Cursor neither entering nor leaving hardware proximity.\n");
1818 }
1819
1820 GHOST_Wintab *wt = window->getWintab();
1821 if (wt) {
1822 bool inRange = LOWORD(lParam);
1823 if (inRange) {
1824 /* Some devices don't emit WT_CSRCHANGE events, so update cursor info here. */
1825 wt->updateCursorInfo();
1826 }
1827 else {
1828 wt->leaveRange();
1829 }
1830 }
1831 eventHandled = true;
1832 break;
1833 }
1834 case WT_INFOCHANGE: {
1835 WINTAB_PRINTF("HWND %p HCTX %p WT_INFOCHANGE\n", window->getHWND(), (void *)wParam);
1836 GHOST_Wintab *wt = window->getWintab();
1837 if (wt) {
1838 wt->processInfoChange(lParam);
1839
1840 if (window->usingTabletAPI(GHOST_kTabletWintab)) {
1841 window->resetPointerPenInfo();
1842 }
1843 }
1844 eventHandled = true;
1845 break;
1846 }
1847 case WT_PACKET: {
1848 processWintabEvent(window);
1849 eventHandled = true;
1850 break;
1851 }
1852 /* ====================
1853 * Wintab events, debug
1854 * ==================== */
1855 case WT_CTXOPEN: {
1856 WINTAB_PRINTF("HWND %p HCTX %p WT_CTXOPEN\n", window->getHWND(), (void *)wParam);
1857 break;
1858 }
1859 case WT_CTXCLOSE: {
1860 WINTAB_PRINTF("HWND %p HCTX %p WT_CTXCLOSE\n", window->getHWND(), (void *)wParam);
1861 break;
1862 }
1863 case WT_CTXUPDATE: {
1864 WINTAB_PRINTF("HWND %p HCTX %p WT_CTXUPDATE\n", window->getHWND(), (void *)wParam);
1865 break;
1866 }
1867 case WT_CTXOVERLAP: {
1868 WINTAB_PRINTF("HWND %p HCTX %p WT_CTXOVERLAP", window->getHWND(), (void *)wParam);
1869 switch (lParam) {
1870 case CXS_DISABLED: {
1871 WINTAB_PRINTF(" CXS_DISABLED\n");
1872 break;
1873 }
1874 case CXS_OBSCURED: {
1875 WINTAB_PRINTF(" CXS_OBSCURED\n");
1876 break;
1877 }
1878 case CXS_ONTOP: {
1879 WINTAB_PRINTF(" CXS_ONTOP\n");
1880 break;
1881 }
1882 }
1883 break;
1884 }
1885 /* =========================
1886 * Pointer events, processed
1887 * ========================= */
1888 case WM_POINTERUPDATE:
1889 case WM_POINTERDOWN:
1890 case WM_POINTERUP: {
1891 processPointerEvent(msg, window, wParam, lParam, eventHandled);
1892 break;
1893 }
1894 case WM_POINTERLEAVE: {
1895 uint32_t pointerId = GET_POINTERID_WPARAM(wParam);
1896 POINTER_INFO pointerInfo;
1897 if (!GetPointerInfo(pointerId, &pointerInfo)) {
1898 break;
1899 }
1900
1901 /* Reset pointer pen info if pen device has left tracking range. */
1902 if (pointerInfo.pointerType == PT_PEN) {
1903 window->resetPointerPenInfo();
1904 eventHandled = true;
1905 }
1906 break;
1907 }
1908 /* =======================
1909 * Mouse events, processed
1910 * ======================= */
1911 case WM_LBUTTONDOWN: {
1913 break;
1914 }
1915 case WM_MBUTTONDOWN: {
1917 break;
1918 }
1919 case WM_RBUTTONDOWN: {
1921 break;
1922 }
1923 case WM_XBUTTONDOWN: {
1924 if (short(HIWORD(wParam)) == XBUTTON1) {
1926 }
1927 else if (short(HIWORD(wParam)) == XBUTTON2) {
1929 }
1930 break;
1931 }
1932 case WM_LBUTTONUP: {
1934 break;
1935 }
1936 case WM_MBUTTONUP: {
1938 break;
1939 }
1940 case WM_RBUTTONUP: {
1942 break;
1943 }
1944 case WM_XBUTTONUP: {
1945 if (short(HIWORD(wParam)) == XBUTTON1) {
1947 }
1948 else if (short(HIWORD(wParam)) == XBUTTON2) {
1950 }
1951 break;
1952 }
1953 case WM_MOUSEMOVE: {
1954 if (!window->m_mousePresent) {
1955 WINTAB_PRINTF("HWND %p mouse enter\n", window->getHWND());
1956 TRACKMOUSEEVENT tme = {sizeof(tme)};
1957 /* Request WM_MOUSELEAVE message when the cursor leaves the client area. */
1958 tme.dwFlags = TME_LEAVE;
1959 if (system->m_autoFocus) {
1960 /* Request WM_MOUSEHOVER message after 100ms when in the client area. */
1961 tme.dwFlags |= TME_HOVER;
1962 tme.dwHoverTime = 100;
1963 }
1964 tme.hwndTrack = hwnd;
1965 TrackMouseEvent(&tme);
1966 window->m_mousePresent = true;
1967 GHOST_Wintab *wt = window->getWintab();
1968 if (wt) {
1969 wt->gainFocus();
1970 }
1971 }
1972
1973 const int32_t window_co[2] = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
1974 int32_t screen_co[2];
1975 window->clientToScreen(UNPACK2(window_co), UNPACK2(screen_co));
1976 event = processCursorEvent(window, screen_co);
1977
1978 break;
1979 }
1980 case WM_MOUSEHOVER: {
1981 /* Mouse Tracking is now off. TrackMouseEvent restarts in MouseMove. */
1982 window->m_mousePresent = false;
1983
1984 /* Auto-focus only occurs within Blender windows, not with _other_ applications. We are
1985 * notified of change of focus from our console, but it returns null from GetFocus. */
1986 HWND old_hwnd = ::GetFocus();
1987 if (old_hwnd && hwnd != old_hwnd) {
1988 HWND new_parent = ::GetParent(hwnd);
1989 HWND old_parent = ::GetParent(old_hwnd);
1990 if (hwnd == old_parent || old_hwnd == new_parent) {
1991 /* Child to its parent, parent to its child. */
1992 ::SetFocus(hwnd);
1993 }
1994 else if (new_parent != HWND_DESKTOP && new_parent == old_parent) {
1995 /* Between siblings of same parent. */
1996 ::SetFocus(hwnd);
1997 }
1998 else if (!new_parent && !old_parent) {
1999 /* Between main windows that don't overlap. */
2000 RECT new_rect, old_rect, dest_rect;
2001
2002 /* The rects without the outside shadows and slightly inset. */
2003 DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &new_rect, sizeof(RECT));
2004 ::InflateRect(&new_rect, -1, -1);
2005 DwmGetWindowAttribute(
2006 old_hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &old_rect, sizeof(RECT));
2007 ::InflateRect(&old_rect, -1, -1);
2008
2009 if (!IntersectRect(&dest_rect, &new_rect, &old_rect)) {
2010 ::SetFocus(hwnd);
2011 }
2012 }
2013 }
2014 break;
2015 }
2016 case WM_MOUSEWHEEL: {
2017 /* The WM_MOUSEWHEEL message is sent to the focus window
2018 * when the mouse wheel is rotated. The DefWindowProc
2019 * function propagates the message to the window's parent.
2020 * There should be no internal forwarding of the message,
2021 * since DefWindowProc propagates it up the parent chain
2022 * until it finds a window that processes it.
2023 */
2024 processWheelEventVertical(window, wParam, lParam);
2025 eventHandled = true;
2026#ifdef BROKEN_PEEK_TOUCHPAD
2027 PostMessage(hwnd, WM_USER, 0, 0);
2028#endif
2029 break;
2030 }
2031 case WM_MOUSEHWHEEL: {
2032 processWheelEventHorizontal(window, wParam, lParam);
2033 eventHandled = true;
2034 break;
2035 }
2036 case WM_SETCURSOR: {
2037 /* The WM_SETCURSOR message is sent to a window if the mouse causes the cursor
2038 * to move within a window and mouse input is not captured.
2039 * This means we have to set the cursor shape every time the mouse moves!
2040 * The DefWindowProc function uses this message to set the cursor to an
2041 * arrow if it is not in the client area.
2042 */
2043 if (LOWORD(lParam) == HTCLIENT) {
2044 /* Load the current cursor. */
2045 window->loadCursor(window->getCursorVisibility(), window->getCursorShape());
2046 /* Bypass call to #DefWindowProc. */
2047 return 0;
2048 }
2049 else {
2050 /* Outside of client area show standard cursor. */
2052 }
2053 break;
2054 }
2055 case WM_MOUSELEAVE: {
2056 WINTAB_PRINTF("HWND %p mouse leave\n", window->getHWND());
2057 window->m_mousePresent = false;
2058 if (window->getTabletData().Active == GHOST_kTabletModeNone) {
2059 /* FIXME: document why the cursor motion event on mouse leave is needed. */
2060 int32_t screen_co[2] = {0, 0};
2061 system->getCursorPosition(screen_co[0], screen_co[1]);
2062 event = processCursorEvent(window, screen_co);
2063 }
2064 GHOST_Wintab *wt = window->getWintab();
2065 if (wt) {
2066 wt->loseFocus();
2067 }
2068 break;
2069 }
2070 /* =====================
2071 * Mouse events, ignored
2072 * ===================== */
2073 case WM_NCMOUSEMOVE: {
2074 /* The WM_NCMOUSEMOVE message is posted to a window when the cursor is moved
2075 * within the non-client area of the window. This message is posted to the window that
2076 * contains the cursor. If a window has captured the mouse, this message is not posted.
2077 */
2078 }
2079 case WM_NCHITTEST: {
2080 /* The WM_NCHITTEST message is sent to a window when the cursor moves, or
2081 * when a mouse button is pressed or released. If the mouse is not captured,
2082 * the message is sent to the window beneath the cursor. Otherwise, the message
2083 * is sent to the window that has captured the mouse.
2084 */
2085 break;
2086 }
2087 /* ========================
2088 * Window events, processed
2089 * ======================== */
2090 case WM_CLOSE: {
2091 /* The WM_CLOSE message is sent as a signal that a window
2092 * or an application should terminate. Restore if minimized. */
2093 if (IsIconic(hwnd)) {
2094 ShowWindow(hwnd, SW_RESTORE);
2095 }
2097 break;
2098 }
2099 case WM_ACTIVATE: {
2100 /* The WM_ACTIVATE message is sent to both the window being activated and the window
2101 * being deactivated. If the windows use the same input queue, the message is sent
2102 * synchronously, first to the window procedure of the top-level window being
2103 * deactivated, then to the window procedure of the top-level window being activated.
2104 * If the windows use different input queues, the message is sent asynchronously,
2105 * so the window is activated immediately. */
2106
2107 system->m_wheelDeltaAccumVertical = 0;
2108 system->m_wheelDeltaAccumHorizontal = 0;
2109 event = processWindowEvent(
2110 LOWORD(wParam) ? GHOST_kEventWindowActivate : GHOST_kEventWindowDeactivate, window);
2111 /* WARNING: Let DefWindowProc handle WM_ACTIVATE, otherwise WM_MOUSEWHEEL
2112 * will not be dispatched to OUR active window if we minimize one of OUR windows. */
2113 if (LOWORD(wParam) == WA_INACTIVE) {
2114 window->lostMouseCapture();
2115 }
2116
2117 lResult = ::DefWindowProc(hwnd, msg, wParam, lParam);
2118 break;
2119 }
2120 case WM_ENTERSIZEMOVE: {
2121 /* The WM_ENTERSIZEMOVE message is sent one time to a window after it enters the moving
2122 * or sizing modal loop. The window enters the moving or sizing modal loop when the user
2123 * clicks the window's title bar or sizing border, or when the window passes the
2124 * WM_SYSCOMMAND message to the DefWindowProc function and the wParam parameter of the
2125 * message specifies the SC_MOVE or SC_SIZE value. The operation is complete when
2126 * DefWindowProc returns.
2127 */
2128 window->m_inLiveResize = 1;
2129 break;
2130 }
2131 case WM_EXITSIZEMOVE: {
2132 window->m_inLiveResize = 0;
2133 break;
2134 }
2135 case WM_PAINT: {
2136 /* An application sends the WM_PAINT message when the system or another application
2137 * makes a request to paint a portion of an application's window. The message is sent
2138 * when the UpdateWindow or RedrawWindow function is called, or by the DispatchMessage
2139 * function when the application obtains a WM_PAINT message by using the GetMessage or
2140 * PeekMessage function.
2141 */
2142 if (!window->m_inLiveResize) {
2144 ::ValidateRect(hwnd, nullptr);
2145 }
2146 else {
2147 eventHandled = true;
2148 }
2149 break;
2150 }
2151 case WM_GETMINMAXINFO: {
2152 /* The WM_GETMINMAXINFO message is sent to a window when the size or
2153 * position of the window is about to change. An application can use
2154 * this message to override the window's default maximized size and
2155 * position, or its default minimum or maximum tracking size.
2156 */
2157 processMinMaxInfo((MINMAXINFO *)lParam);
2158 /* Let DefWindowProc handle it. */
2159 break;
2160 }
2161 case WM_SIZING: {
2162 event = processWindowSizeEvent(window);
2163 break;
2164 }
2165 case WM_SIZE: {
2166 /* The WM_SIZE message is sent to a window after its size has changed.
2167 * The WM_SIZE and WM_MOVE messages are not sent if an application handles the
2168 * WM_WINDOWPOSCHANGED message without calling DefWindowProc. It is more efficient
2169 * to perform any move or size change processing during the WM_WINDOWPOSCHANGED
2170 * message without calling DefWindowProc.
2171 */
2172 event = processWindowSizeEvent(window);
2173 break;
2174 }
2175 case WM_CAPTURECHANGED: {
2176 window->lostMouseCapture();
2177 break;
2178 }
2179 case WM_MOVING:
2180 /* The WM_MOVING message is sent to a window that the user is moving. By processing
2181 * this message, an application can monitor the size and position of the drag rectangle
2182 * and, if needed, change its size or position.
2183 */
2184 case WM_MOVE: {
2185 /* The WM_SIZE and WM_MOVE messages are not sent if an application handles the
2186 * WM_WINDOWPOSCHANGED message without calling DefWindowProc. It is more efficient
2187 * to perform any move or size change processing during the WM_WINDOWPOSCHANGED
2188 * message without calling DefWindowProc.
2189 */
2190 /* See #WM_SIZE comment. */
2191 if (window->m_inLiveResize) {
2193 system->dispatchEvents();
2194 }
2195 else {
2197 }
2198
2199 break;
2200 }
2201 case WM_DPICHANGED: {
2202 /* The WM_DPICHANGED message is sent when the effective dots per inch (dpi) for a
2203 * window has changed. The DPI is the scale factor for a window. There are multiple
2204 * events that can cause the DPI to change such as when the window is moved to a monitor
2205 * with a different DPI. */
2206
2207 /* The suggested new size and position of the window. */
2208 RECT *const suggestedWindowRect = (RECT *)lParam;
2209
2210 /* Push DPI change event first. */
2212 system->dispatchEvents();
2213 eventHandled = true;
2214
2215 /* Then move and resize window. */
2216 SetWindowPos(hwnd,
2217 nullptr,
2218 suggestedWindowRect->left,
2219 suggestedWindowRect->top,
2220 suggestedWindowRect->right - suggestedWindowRect->left,
2221 suggestedWindowRect->bottom - suggestedWindowRect->top,
2222 SWP_NOZORDER | SWP_NOACTIVATE);
2223
2224 window->updateDPI();
2225 break;
2226 }
2227 case WM_DISPLAYCHANGE: {
2228 GHOST_Wintab *wt = window->getWintab();
2229 if (wt) {
2230 wt->remapCoordinates();
2231 }
2232 break;
2233 }
2234 case WM_KILLFOCUS: {
2235 /* The WM_KILLFOCUS message is sent to a window immediately before it loses the keyboard
2236 * focus. We want to prevent this if a window is still active and it loses focus to
2237 * nowhere. */
2238 if (!wParam && hwnd == ::GetActiveWindow()) {
2239 ::SetFocus(hwnd);
2240 }
2241 break;
2242 }
2243 case WM_SETTINGCHANGE: {
2244 /* Microsoft: "Note that some applications send this message with lParam set to nullptr"
2245 */
2246 if (((void *)lParam != nullptr) && (wcscmp(LPCWSTR(lParam), L"ImmersiveColorSet") == 0))
2247 {
2248 window->ThemeRefresh();
2249 }
2250 break;
2251 }
2252 /* ======================
2253 * Window events, ignored
2254 * ====================== */
2255 case WM_WINDOWPOSCHANGED:
2256 /* The WM_WINDOWPOSCHANGED message is sent to a window whose size, position, or place
2257 * in the Z order has changed as a result of a call to the SetWindowPos function or
2258 * another window-management function.
2259 * The WM_SIZE and WM_MOVE messages are not sent if an application handles the
2260 * WM_WINDOWPOSCHANGED message without calling DefWindowProc. It is more efficient
2261 * to perform any move or size change processing during the WM_WINDOWPOSCHANGED
2262 * message without calling DefWindowProc.
2263 */
2264 case WM_ERASEBKGND:
2265 /* An application sends the WM_ERASEBKGND message when the window background must be
2266 * erased (for example, when a window is resized). The message is sent to prepare an
2267 * invalidated portion of a window for painting. */
2268 {
2269 HBRUSH bgBrush = (HBRUSH)GetClassLongPtr(hwnd, GCLP_HBRBACKGROUND);
2270 if (bgBrush) {
2271 RECT rect;
2272 GetClientRect(hwnd, &rect);
2273 FillRect((HDC)(wParam), &rect, bgBrush);
2274 /* Clear the background brush after the initial fill as we don't
2275 * need or want any default Windows fill behavior on redraw. */
2276 SetClassLongPtr(hwnd, GCLP_HBRBACKGROUND, (LONG_PTR) nullptr);
2277 }
2278 break;
2279 }
2280 case WM_NCPAINT:
2281 /* An application sends the WM_NCPAINT message to a window
2282 * when its frame must be painted. */
2283 case WM_NCACTIVATE:
2284 /* The WM_NCACTIVATE message is sent to a window when its non-client area needs to be
2285 * changed to indicate an active or inactive state. */
2286 case WM_DESTROY:
2287 /* The WM_DESTROY message is sent when a window is being destroyed. It is sent to the
2288 * window procedure of the window being destroyed after the window is removed from the
2289 * screen. This message is sent first to the window being destroyed and then to the child
2290 * windows (if any) as they are destroyed. During the processing of the message, it can
2291 * be assumed that all child windows still exist. */
2292 case WM_NCDESTROY: {
2293 /* The WM_NCDESTROY message informs a window that its non-client area is being
2294 * destroyed. The DestroyWindow function sends the WM_NCDESTROY message to the window
2295 * following the WM_DESTROY message. WM_DESTROY is used to free the allocated memory
2296 * object associated with the window.
2297 */
2298 break;
2299 }
2300 case WM_SHOWWINDOW:
2301 /* The WM_SHOWWINDOW message is sent to a window when the window is
2302 * about to be hidden or shown. */
2303 case WM_WINDOWPOSCHANGING:
2304 /* The WM_WINDOWPOSCHANGING message is sent to a window whose size, position, or place in
2305 * the Z order is about to change as a result of a call to the SetWindowPos function or
2306 * another window-management function. */
2307 case WM_SETFOCUS: {
2308 /* The WM_SETFOCUS message is sent to a window after it has gained the keyboard focus. */
2309 break;
2310 }
2311 /* ============
2312 * Other events
2313 * ============ */
2314 case WM_GETTEXT:
2315 /* An application sends a WM_GETTEXT message to copy the text that
2316 * corresponds to a window into a buffer provided by the caller. */
2317 case WM_ACTIVATEAPP:
2318 /* The WM_ACTIVATEAPP message is sent when a window belonging to a
2319 * different application than the active window is about to be activated.
2320 * The message is sent to the application whose window is being activated
2321 * and to the application whose window is being deactivated. */
2322 case WM_TIMER: {
2323 /* The WIN32 docs say:
2324 * The WM_TIMER message is posted to the installing thread's message queue
2325 * when a timer expires. You can process the message by providing a WM_TIMER
2326 * case in the window procedure. Otherwise, the default window procedure will
2327 * call the TimerProc callback function specified in the call to the SetTimer
2328 * function used to install the timer.
2329 *
2330 * In GHOST, we let DefWindowProc call the timer callback. */
2331 break;
2332 }
2333 case DM_POINTERHITTEST: {
2334 /* The DM_POINTERHITTEST message is sent to a window, when pointer input is first
2335 * detected, in order to determine the most probable input target for Direct
2336 * Manipulation. */
2337 if (system->m_multitouchGestures) {
2338 window->onPointerHitTest(wParam);
2339 }
2340 break;
2341 }
2342 }
2343 }
2344 else {
2345 /* Event found for a window before the pointer to the class has been set. */
2346 GHOST_PRINT("GHOST_SystemWin32::wndProc: GHOST window event before creation\n");
2347 /* These are events we typically miss at this point:
2348 * WM_GETMINMAXINFO 0x24
2349 * WM_NCCREATE 0x81
2350 * WM_NCCALCSIZE 0x83
2351 * WM_CREATE 0x01
2352 * We let DefWindowProc do the work.
2353 */
2354 }
2355 }
2356 else {
2357 /* Events without valid `hwnd`. */
2358 GHOST_PRINT("GHOST_SystemWin32::wndProc: event without window\n");
2359 }
2360
2361 if (event) {
2362 system->pushEvent(event);
2363 eventHandled = true;
2364 }
2365
2366 if (!eventHandled) {
2367 lResult = ::DefWindowProcW(hwnd, msg, wParam, lParam);
2368 }
2369
2370 return lResult;
2371}
2372
2373char *GHOST_SystemWin32::getClipboard(bool /*selection*/) const
2374{
2375 if (IsClipboardFormatAvailable(CF_UNICODETEXT) && OpenClipboard(nullptr)) {
2376 wchar_t *buffer;
2377 HANDLE hData = GetClipboardData(CF_UNICODETEXT);
2378 if (hData == nullptr) {
2379 CloseClipboard();
2380 return nullptr;
2381 }
2382 buffer = (wchar_t *)GlobalLock(hData);
2383 if (!buffer) {
2384 CloseClipboard();
2385 return nullptr;
2386 }
2387
2388 char *temp_buff = alloc_utf_8_from_16(buffer, 0);
2389
2390 /* Buffer mustn't be accessed after CloseClipboard
2391 * it would like accessing free-d memory */
2392 GlobalUnlock(hData);
2393 CloseClipboard();
2394
2395 return temp_buff;
2396 }
2397 if (IsClipboardFormatAvailable(CF_TEXT) && OpenClipboard(nullptr)) {
2398 char *buffer;
2399 size_t len = 0;
2400 HANDLE hData = GetClipboardData(CF_TEXT);
2401 if (hData == nullptr) {
2402 CloseClipboard();
2403 return nullptr;
2404 }
2405 buffer = (char *)GlobalLock(hData);
2406 if (!buffer) {
2407 CloseClipboard();
2408 return nullptr;
2409 }
2410
2411 len = strlen(buffer);
2412 char *temp_buff = (char *)malloc(len + 1);
2413 memcpy(temp_buff, buffer, len + 1);
2414
2415 /* Buffer mustn't be accessed after CloseClipboard
2416 * it would like accessing free-d memory */
2417 GlobalUnlock(hData);
2418 CloseClipboard();
2419
2420 return temp_buff;
2421 }
2422 return nullptr;
2423}
2424
2425void GHOST_SystemWin32::putClipboard(const char *buffer, bool selection) const
2426{
2427 if (selection || !buffer) {
2428 return;
2429 } /* For copying the selection, used on X11. */
2430
2431 if (OpenClipboard(nullptr)) {
2432 EmptyClipboard();
2433
2434 /* Get length of buffer including the terminating null. */
2435 size_t len = count_utf_16_from_8(buffer);
2436
2437 HGLOBAL clipbuffer = GlobalAlloc(GMEM_MOVEABLE, sizeof(wchar_t) * len);
2438 if (clipbuffer) {
2439 wchar_t *data = (wchar_t *)GlobalLock(clipbuffer);
2440
2441 conv_utf_8_to_16(buffer, data, len);
2442
2443 GlobalUnlock(clipbuffer);
2444 SetClipboardData(CF_UNICODETEXT, clipbuffer);
2445 }
2446
2447 CloseClipboard();
2448 }
2449}
2450
2452{
2453 if (IsClipboardFormatAvailable(CF_DIBV5) ||
2454 IsClipboardFormatAvailable(RegisterClipboardFormat("PNG")))
2455 {
2456 return GHOST_kSuccess;
2457 }
2458
2459 /* This could be a file path to an image file. */
2461 if (IsClipboardFormatAvailable(CF_HDROP)) {
2462 if (OpenClipboard(nullptr)) {
2463 if (HANDLE hGlobal = GetClipboardData(CF_HDROP)) {
2464 if (HDROP hDrop = static_cast<HDROP>(GlobalLock(hGlobal))) {
2465 UINT fileCount = DragQueryFile(hDrop, 0xffffffff, nullptr, 0);
2466 if (fileCount == 1) {
2467 WCHAR lpszFile[MAX_PATH] = {0};
2468 DragQueryFileW(hDrop, 0, lpszFile, MAX_PATH);
2469 char *filepath = alloc_utf_8_from_16(lpszFile, 0);
2470 ImBuf *ibuf = IMB_load_image_from_filepath(filepath,
2472 free(filepath);
2473 if (ibuf) {
2474 IMB_freeImBuf(ibuf);
2476 }
2477 }
2478 GlobalUnlock(hGlobal);
2479 }
2480 }
2481 CloseClipboard();
2482 }
2483 }
2484 return result;
2485}
2486
2487static uint *getClipboardImageFilepath(int *r_width, int *r_height)
2488{
2489 char *filepath = nullptr;
2490
2491 if (OpenClipboard(nullptr)) {
2492 if (HANDLE hGlobal = GetClipboardData(CF_HDROP)) {
2493 if (HDROP hDrop = static_cast<HDROP>(GlobalLock(hGlobal))) {
2494 UINT fileCount = DragQueryFile(hDrop, 0xffffffff, nullptr, 0);
2495 if (fileCount == 1) {
2496 WCHAR lpszFile[MAX_PATH] = {0};
2497 DragQueryFileW(hDrop, 0, lpszFile, MAX_PATH);
2498 filepath = alloc_utf_8_from_16(lpszFile, 0);
2499 }
2500 GlobalUnlock(hGlobal);
2501 }
2502 }
2503 CloseClipboard();
2504 }
2505
2506 if (filepath) {
2508 free(filepath);
2509 if (ibuf) {
2510 *r_width = ibuf->x;
2511 *r_height = ibuf->y;
2512 const uint64_t byte_count = static_cast<uint64_t>(ibuf->x) * ibuf->y * 4;
2513 uint *rgba = static_cast<uint *>(malloc(byte_count));
2514 if (rgba) {
2515 memcpy(rgba, ibuf->byte_buffer.data, byte_count);
2516 }
2517 IMB_freeImBuf(ibuf);
2518 return rgba;
2519 }
2520 }
2521
2522 return nullptr;
2523}
2524
2525static uint *getClipboardImageDibV5(int *r_width, int *r_height)
2526{
2527 HANDLE hGlobal = GetClipboardData(CF_DIBV5);
2528 if (hGlobal == nullptr) {
2529 return nullptr;
2530 }
2531
2532 BITMAPV5HEADER *bitmapV5Header = (BITMAPV5HEADER *)GlobalLock(hGlobal);
2533 if (bitmapV5Header == nullptr) {
2534 return nullptr;
2535 }
2536
2537 int offset = bitmapV5Header->bV5Size + bitmapV5Header->bV5ClrUsed * sizeof(RGBQUAD);
2538
2539 BYTE *buffer = (BYTE *)bitmapV5Header + offset;
2540 int bitcount = bitmapV5Header->bV5BitCount;
2541 int width = bitmapV5Header->bV5Width;
2542 int height = bitmapV5Header->bV5Height;
2543
2544 /* Clipboard data is untrusted. Protect against arithmetic overflow as DibV5
2545 * only supports up to DWORD size bytes. */
2546 if (uint64_t(width) * uint64_t(height) > (std::numeric_limits<DWORD>::max() / 4)) {
2547 GlobalUnlock(hGlobal);
2548 return nullptr;
2549 }
2550
2551 *r_width = width;
2552 *r_height = height;
2553
2554 DWORD ColorMasks[4];
2555 ColorMasks[0] = bitmapV5Header->bV5RedMask ? bitmapV5Header->bV5RedMask : 0xff;
2556 ColorMasks[1] = bitmapV5Header->bV5GreenMask ? bitmapV5Header->bV5GreenMask : 0xff00;
2557 ColorMasks[2] = bitmapV5Header->bV5BlueMask ? bitmapV5Header->bV5BlueMask : 0xff0000;
2558 ColorMasks[3] = bitmapV5Header->bV5AlphaMask ? bitmapV5Header->bV5AlphaMask : 0xff000000;
2559
2560 /* Bit shifts needed for the ColorMasks. */
2561 DWORD ColorShifts[4];
2562 for (int i = 0; i < 4; i++) {
2563 _BitScanForward(&ColorShifts[i], ColorMasks[i]);
2564 }
2565
2566 uchar *source = (uchar *)buffer;
2567 uint *rgba = (uint *)malloc(uint64_t(width) * height * 4);
2568 uint8_t *target = (uint8_t *)rgba;
2569
2570 if (bitmapV5Header->bV5Compression == BI_BITFIELDS && bitcount == 32) {
2571 for (int h = 0; h < height; h++) {
2572 for (int w = 0; w < width; w++, target += 4, source += 4) {
2573 DWORD *pix = (DWORD *)source;
2574 target[0] = uint8_t((*pix & ColorMasks[0]) >> ColorShifts[0]);
2575 target[1] = uint8_t((*pix & ColorMasks[1]) >> ColorShifts[1]);
2576 target[2] = uint8_t((*pix & ColorMasks[2]) >> ColorShifts[2]);
2577 target[3] = uint8_t((*pix & ColorMasks[3]) >> ColorShifts[3]);
2578 }
2579 }
2580 }
2581 else if (bitmapV5Header->bV5Compression == BI_RGB && bitcount == 32) {
2582 for (int h = 0; h < height; h++) {
2583 for (int w = 0; w < width; w++, target += 4, source += 4) {
2584 RGBQUAD *quad = (RGBQUAD *)source;
2585 target[0] = uint8_t(quad->rgbRed);
2586 target[1] = uint8_t(quad->rgbGreen);
2587 target[2] = uint8_t(quad->rgbBlue);
2588 target[3] = (bitmapV5Header->bV5AlphaMask) ? uint8_t(quad->rgbReserved) : 255;
2589 }
2590 }
2591 }
2592 else if (bitmapV5Header->bV5Compression == BI_RGB && bitcount == 24) {
2593 int bytes_per_row = ((((width * bitcount) + 31) & ~31) >> 3);
2594 int slack = bytes_per_row - (width * 3);
2595 for (int h = 0; h < height; h++, source += slack) {
2596 for (int w = 0; w < width; w++, target += 4, source += 3) {
2597 RGBTRIPLE *triple = (RGBTRIPLE *)source;
2598 target[0] = uint8_t(triple->rgbtRed);
2599 target[1] = uint8_t(triple->rgbtGreen);
2600 target[2] = uint8_t(triple->rgbtBlue);
2601 target[3] = 255;
2602 }
2603 }
2604 }
2605
2606 GlobalUnlock(hGlobal);
2607 return rgba;
2608}
2609
2610/* Works with any image format that ImBuf can load. */
2611static uint *getClipboardImageImBuf(int *r_width, int *r_height, UINT format)
2612{
2613 HANDLE hGlobal = GetClipboardData(format);
2614 if (hGlobal == nullptr) {
2615 return nullptr;
2616 }
2617
2618 LPVOID pMem = GlobalLock(hGlobal);
2619 if (!pMem) {
2620 return nullptr;
2621 }
2622
2623 uint *rgba = nullptr;
2624
2626 (uchar *)pMem, GlobalSize(hGlobal), IB_byte_data, "<clipboard>");
2627
2628 if (ibuf) {
2629 *r_width = ibuf->x;
2630 *r_height = ibuf->y;
2631 const uint64_t byte_count = uint64_t(ibuf->x) * ibuf->y * 4;
2632 rgba = (uint *)malloc(byte_count);
2633 memcpy(rgba, ibuf->byte_buffer.data, byte_count);
2634 IMB_freeImBuf(ibuf);
2635 }
2636
2637 GlobalUnlock(hGlobal);
2638 return rgba;
2639}
2640
2641uint *GHOST_SystemWin32::getClipboardImage(int *r_width, int *r_height) const
2642{
2643 if (IsClipboardFormatAvailable(CF_HDROP)) {
2644 return getClipboardImageFilepath(r_width, r_height);
2645 }
2646
2647 if (!OpenClipboard(nullptr)) {
2648 return nullptr;
2649 }
2650
2651 /* Synthesized formats are placed after posted formats. */
2652 UINT cfPNG = RegisterClipboardFormat("PNG");
2653 UINT format = 0;
2654 for (int cf = EnumClipboardFormats(0); cf; cf = EnumClipboardFormats(cf)) {
2655 if (ELEM(cf, CF_DIBV5, cfPNG)) {
2656 format = cf;
2657 }
2658 if (cf == CF_DIBV5 || (cf == CF_BITMAP && format == cfPNG)) {
2659 break; /* Favor CF_DIBV5, but not if synthesized. */
2660 }
2661 }
2662
2663 uint *rgba = nullptr;
2664
2665 if (format == CF_DIBV5) {
2666 rgba = getClipboardImageDibV5(r_width, r_height);
2667 }
2668 else if (format == cfPNG) {
2669 rgba = getClipboardImageImBuf(r_width, r_height, cfPNG);
2670 }
2671 else {
2672 *r_width = 0;
2673 *r_height = 0;
2674 }
2675
2676 CloseClipboard();
2677 return rgba;
2678}
2679
2680static bool putClipboardImageDibV5(uint *rgba, int width, int height)
2681{
2682 /* DibV5 only supports up to DWORD size bytes. Skip processing entirely
2683 * in case of overflow but return true to the caller to allow PNG to be
2684 * used on its own. */
2685 if (uint64_t(width) * uint64_t(height) > (std::numeric_limits<DWORD>::max() / 4)) {
2686 return true;
2687 }
2688
2689 DWORD size_pixels = width * height * 4;
2690
2691 HGLOBAL hMem = GlobalAlloc(GHND, sizeof(BITMAPV5HEADER) + size_pixels);
2692 if (!hMem) {
2693 return false;
2694 }
2695
2696 BITMAPV5HEADER *hdr = (BITMAPV5HEADER *)GlobalLock(hMem);
2697 if (!hdr) {
2698 GlobalFree(hMem);
2699 return false;
2700 }
2701
2702 hdr->bV5Size = sizeof(BITMAPV5HEADER);
2703 hdr->bV5Width = width;
2704 hdr->bV5Height = height;
2705 hdr->bV5Planes = 1;
2706 hdr->bV5BitCount = 32;
2707 hdr->bV5SizeImage = size_pixels;
2708 hdr->bV5Compression = BI_BITFIELDS;
2709 hdr->bV5RedMask = 0x000000ff;
2710 hdr->bV5GreenMask = 0x0000ff00;
2711 hdr->bV5BlueMask = 0x00ff0000;
2712 hdr->bV5AlphaMask = 0xff000000;
2713 hdr->bV5CSType = LCS_sRGB;
2714 hdr->bV5Intent = LCS_GM_IMAGES;
2715 hdr->bV5ClrUsed = 0;
2716
2717 memcpy((char *)hdr + sizeof(BITMAPV5HEADER), rgba, size_pixels);
2718
2719 GlobalUnlock(hMem);
2720
2721 if (!SetClipboardData(CF_DIBV5, hMem)) {
2722 GlobalFree(hMem);
2723 return false;
2724 }
2725
2726 return true;
2727}
2728
2729static bool putClipboardImagePNG(uint *rgba, int width, int height)
2730{
2731 UINT cf = RegisterClipboardFormat("PNG");
2732
2733 /* Load buffer into ImBuf, convert to PNG. */
2734 ImBuf *ibuf = IMB_allocFromBuffer(reinterpret_cast<uint8_t *>(rgba), nullptr, width, height, 32);
2735 ibuf->ftype = IMB_FTYPE_PNG;
2736 ibuf->foptions.quality = 15;
2737 if (!IMB_save_image(ibuf, "<memory>", IB_byte_data | IB_mem)) {
2738 IMB_freeImBuf(ibuf);
2739 return false;
2740 }
2741
2742 HGLOBAL hMem = GlobalAlloc(GHND, ibuf->encoded_buffer_size);
2743 if (!hMem) {
2744 IMB_freeImBuf(ibuf);
2745 return false;
2746 }
2747
2748 LPVOID pMem = GlobalLock(hMem);
2749 if (!pMem) {
2750 IMB_freeImBuf(ibuf);
2751 GlobalFree(hMem);
2752 return false;
2753 }
2754
2755 memcpy(pMem, ibuf->encoded_buffer.data, ibuf->encoded_buffer_size);
2756
2757 GlobalUnlock(hMem);
2758 IMB_freeImBuf(ibuf);
2759
2760 if (!SetClipboardData(cf, hMem)) {
2761 GlobalFree(hMem);
2762 return false;
2763 }
2764
2765 return true;
2766}
2767
2769{
2770 if (!OpenClipboard(nullptr) || !EmptyClipboard()) {
2771 return GHOST_kFailure;
2772 }
2773
2774 bool ok = putClipboardImageDibV5(rgba, width, height) &&
2775 putClipboardImagePNG(rgba, width, height);
2776
2777 CloseClipboard();
2778
2779 return (ok) ? GHOST_kSuccess : GHOST_kFailure;
2780}
2781
2782/* -------------------------------------------------------------------- */
2785
2787 const char *message,
2788 const char *help_label,
2789 const char *continue_label,
2790 const char *link,
2791 GHOST_DialogOptions dialog_options) const
2792{
2793 const wchar_t *title_16 = alloc_utf16_from_8(title, 0);
2794 const wchar_t *message_16 = alloc_utf16_from_8(message, 0);
2795 const wchar_t *help_label_16 = alloc_utf16_from_8(help_label, 0);
2796 const wchar_t *continue_label_16 = alloc_utf16_from_8(continue_label, 0);
2797
2798 int nButtonPressed = 0;
2799 TASKDIALOGCONFIG config = {0};
2800 const TASKDIALOG_BUTTON buttons[] = {{IDOK, help_label_16}, {IDCONTINUE, continue_label_16}};
2801
2802 config.cbSize = sizeof(config);
2803 config.hInstance = 0;
2804 config.dwCommonButtons = 0;
2805 config.pszMainIcon = (dialog_options & GHOST_DialogError ? TD_ERROR_ICON :
2806 dialog_options & GHOST_DialogWarning ? TD_WARNING_ICON :
2807 TD_INFORMATION_ICON);
2808 config.pszWindowTitle = L"Blender";
2809 config.pszMainInstruction = title_16;
2810 config.pszContent = message_16;
2811 const bool has_link = link && strlen(link);
2812 config.pButtons = has_link ? buttons : buttons + 1;
2813 config.cButtons = has_link ? 2 : 1;
2814
2815 TaskDialogIndirect(&config, &nButtonPressed, nullptr, nullptr);
2816 switch (nButtonPressed) {
2817 case IDOK:
2818 ShellExecute(nullptr, "open", link, nullptr, nullptr, SW_SHOWNORMAL);
2819 break;
2820 case IDCONTINUE:
2821 break;
2822 default:
2823 break; /* Should never happen. */
2824 }
2825
2826 free((void *)title_16);
2827 free((void *)message_16);
2828 free((void *)help_label_16);
2829 free((void *)continue_label_16);
2830
2831 return GHOST_kSuccess;
2832}
2833
2835
2836static DWORD GetParentProcessID(void)
2837{
2838 HANDLE snapshot;
2839 PROCESSENTRY32 pe32 = {0};
2840 DWORD ppid = 0, pid = GetCurrentProcessId();
2841 snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
2842 if (snapshot == INVALID_HANDLE_VALUE) {
2843 return -1;
2844 }
2845 pe32.dwSize = sizeof(pe32);
2846 if (!Process32First(snapshot, &pe32)) {
2847 CloseHandle(snapshot);
2848 return -1;
2849 }
2850 do {
2851 if (pe32.th32ProcessID == pid) {
2852 ppid = pe32.th32ParentProcessID;
2853 break;
2854 }
2855 } while (Process32Next(snapshot, &pe32));
2856 CloseHandle(snapshot);
2857 return ppid;
2858}
2859
2860static bool getProcessName(int pid, char *buffer, int max_len)
2861{
2862 bool result = false;
2863 HANDLE handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
2864 if (handle) {
2865 GetModuleFileNameEx(handle, 0, buffer, max_len);
2866 result = true;
2867 }
2868 CloseHandle(handle);
2869 return result;
2870}
2871
2873{
2874 HWND hwnd = GetConsoleWindow();
2875
2876 if (hwnd) {
2877 DWORD pid = (DWORD)-1;
2878 DWORD ppid = GetParentProcessID();
2879 char parent_name[MAX_PATH];
2880 bool start_from_launcher = false;
2881
2882 GetWindowThreadProcessId(hwnd, &pid);
2883 if (getProcessName(ppid, parent_name, sizeof(parent_name))) {
2884 char *filename = strrchr(parent_name, '\\');
2885 if (filename != nullptr) {
2886 start_from_launcher = strstr(filename, "blender.exe") != nullptr;
2887 }
2888 }
2889
2890 /* When we're starting from a wrapper we need to compare with parent process ID. */
2891 if (pid != (start_from_launcher ? ppid : GetCurrentProcessId())) {
2892 return true;
2893 }
2894 }
2895
2896 return false;
2897}
2898
2900{
2901 HWND wnd = GetConsoleWindow();
2902
2903 switch (action) {
2906 ShowWindow(wnd, SW_HIDE);
2907 m_consoleStatus = false;
2908 }
2909 break;
2910 }
2912 ShowWindow(wnd, SW_HIDE);
2913 m_consoleStatus = false;
2914 break;
2915 }
2917 ShowWindow(wnd, SW_SHOW);
2919 DeleteMenu(GetSystemMenu(wnd, FALSE), SC_CLOSE, MF_BYCOMMAND);
2920 }
2921 m_consoleStatus = true;
2922 break;
2923 }
2925 ShowWindow(wnd, m_consoleStatus ? SW_HIDE : SW_SHOW);
2928 DeleteMenu(GetSystemMenu(wnd, FALSE), SC_CLOSE, MF_BYCOMMAND);
2929 }
2930 break;
2931 }
2932 }
2933
2934 return m_consoleStatus;
2935}
void BLI_kdtree_nd_ free(KDTree *tree)
unsigned char uchar
unsigned int uint
#define UNPACK2(a)
#define ELEM(...)
#define FALSE
#define GHOST_OPENGL_WGL_RESET_NOTIFICATION_STRATEGY
#define GHOST_PRINTF(x,...)
#define GHOST_ASSERT(x, info)
#define GHOST_PRINT(x)
std::array< GHOST_NDOF_ButtonT, 6 > NDOF_Button_Array
@ ShortButton
@ LongButton
#define VK_EQUALS
static uint * getClipboardImageFilepath(int *r_width, int *r_height)
static void initRawInput()
static bool putClipboardImageDibV5(uint *rgba, int width, int height)
#define DEVICE_COUNT
#define VK_BACK_QUOTE
static uint * getClipboardImageImBuf(int *r_width, int *r_height, UINT format)
static const std::map< uint16_t, GHOST_NDOF_ButtonT > longButtonHIDsToGHOST_NDOFButtons
#define VK_CLOSE_BRACKET
static GHOST_NDOF_ButtonT translateLongButtonToNDOFButton(uint16_t longKey)
#define VK_SEMICOLON
#define VK_COMMA
#define VK_SLASH
BOOL(API * GHOST_WIN32_EnableNonClientDpiScaling)(HWND)
static bool isStartedFromCommandPrompt()
#define VK_OPEN_BRACKET
static uint * getClipboardImageDibV5(int *r_width, int *r_height)
static bool putClipboardImagePNG(uint *rgba, int width, int height)
#define VK_BACK_SLASH
#define VK_GR_LESS
#define VK_PERIOD
static uint64_t getMessageTime(GHOST_SystemWin32 *system)
static DWORD GetParentProcessID(void)
static bool getProcessName(int pid, char *buffer, int max_len)
#define VK_MINUS
@ GHOST_kEventWheelAxisVertical
@ GHOST_kEventWheelAxisHorizontal
@ GHOST_kTrackpadEventMagnify
@ GHOST_kTrackpadEventScroll
GHOST_TWindowState
@ GHOST_kWindowStateMinimized
@ GHOST_kStandardCursorDefault
GHOST_NDOF_ButtonT
@ GHOST_NDOF_BUTTON_TILT_CCW
@ GHOST_NDOF_BUTTON_SAVE_V1
@ GHOST_NDOF_BUTTON_ROLL_CCW
@ GHOST_NDOF_BUTTON_BACK
@ GHOST_NDOF_BUTTON_ISO2
@ GHOST_NDOF_BUTTON_SPIN_CCW
@ GHOST_NDOF_BUTTON_SAVE_V3
@ GHOST_NDOF_BUTTON_SAVE_V2
@ GHOST_NDOF_BUTTON_BOTTOM
@ GHOST_NDOF_BUTTON_LEFT
GHOST_TEventType
@ GHOST_kEventWindowClose
@ GHOST_kEventWindowMove
@ GHOST_kEventWindowSize
@ GHOST_kEventImeComposition
@ GHOST_kEventCursorMove
@ GHOST_kEventButtonUp
@ GHOST_kEventWindowActivate
@ GHOST_kEventWindowUpdate
@ GHOST_kEventWindowDeactivate
@ GHOST_kEventButtonDown
@ GHOST_kEventKeyDown
@ GHOST_kEventImeCompositionStart
@ GHOST_kEventImeCompositionEnd
@ GHOST_kEventWindowDPIHintChanged
@ GHOST_kEventKeyUp
static const GHOST_TabletData GHOST_TABLET_DATA_NONE
@ GHOST_kDebugWintab
#define GHOST_KEY_MODIFIER_CHECK(key)
@ GHOST_kTabletModeNone
GHOST_TCapabilityFlag
Definition GHOST_Types.h:89
@ GHOST_kCapabilityKeyboardHyperKey
@ GHOST_kCapabilityPrimaryClipboard
GHOST_TAxisFlag
#define GHOST_CAPABILITY_FLAG_ALL
GHOST_TKey
@ GHOST_kKeyLeftOS
@ GHOST_kKeyInsert
@ GHOST_kKeySemicolon
@ GHOST_kKeyMediaPlay
@ GHOST_kKeyQuote
@ GHOST_kKeyNumpad3
@ GHOST_kKeyAccentGrave
@ GHOST_kKeyNumpad1
@ GHOST_kKeyLeftAlt
@ GHOST_kKeyRightShift
@ GHOST_kKeyNumLock
@ GHOST_kKeyI
@ GHOST_kKeyEnter
@ GHOST_kKeyNumpadSlash
@ GHOST_kKeyRightArrow
@ GHOST_kKeyF13
@ GHOST_kKeyNumpad4
@ GHOST_kKeyPause
@ GHOST_kKeyCapsLock
@ GHOST_kKeyApp
@ GHOST_kKeyMinus
@ GHOST_kKeyMediaStop
@ GHOST_kKeyBackSpace
@ GHOST_kKey0
@ GHOST_kKeyDownPage
@ GHOST_kKeyGrLess
@ GHOST_kKeyDownArrow
@ GHOST_kKeyRightOS
@ GHOST_kKeyNumpadPeriod
@ GHOST_kKeyF1
@ GHOST_kKeyNumpadAsterisk
@ GHOST_kKeyPrintScreen
@ GHOST_kKeyLeftControl
@ GHOST_kKeyLeftBracket
@ GHOST_kKeyTab
@ GHOST_kKeyComma
@ GHOST_kKeyRightBracket
@ GHOST_kKeyBackslash
@ GHOST_kKeyNumpad2
@ GHOST_kKeyRightAlt
@ GHOST_kKeyPeriod
@ GHOST_kKeyNumpadPlus
@ GHOST_kKeyUpPage
@ GHOST_kKeyNumpad5
@ GHOST_kKeyLeftArrow
@ GHOST_kKeyEqual
@ GHOST_kKeyHome
@ GHOST_kKeyNumpad6
@ GHOST_kKeyNumpad8
@ GHOST_kKeyNumpad9
@ GHOST_kKeyEnd
@ GHOST_kKeyUpArrow
@ GHOST_kKeyDelete
@ GHOST_kKeyNumpad0
@ GHOST_kKeyA
@ GHOST_kKeyMediaFirst
@ GHOST_kKeyNumpad7
@ GHOST_kKeyRightControl
@ GHOST_kKeyEsc
@ GHOST_kKeyUnknown
@ GHOST_kKeyScrollLock
@ GHOST_kKeySlash
@ GHOST_kKeyNumpadEnter
@ GHOST_kKeyNumpadMinus
@ GHOST_kKeyLeftShift
@ GHOST_kKeyMediaLast
@ GHOST_kKeySpace
@ GHOST_kModifierKeyRightControl
@ GHOST_kModifierKeyLeftControl
@ GHOST_kModifierKeyRightAlt
@ GHOST_kModifierKeyRightShift
@ GHOST_kModifierKeyLeftAlt
@ GHOST_kModifierKeyLeftShift
@ GHOST_kModifierKeyLeftOS
@ GHOST_kModifierKeyRightOS
GHOST_TSuccess
Definition GHOST_Types.h:80
@ GHOST_kFailure
Definition GHOST_Types.h:80
@ GHOST_kSuccess
Definition GHOST_Types.h:80
@ GHOST_kFireTimeNever
@ GHOST_gpuStereoVisual
Definition GHOST_Types.h:69
@ GHOST_gpuDebugContext
Definition GHOST_Types.h:70
@ GHOST_kGrabHide
GHOST_TDragnDropTypes
GHOST_TButton
@ GHOST_kButtonMaskRight
@ GHOST_kButtonMaskButton4
@ GHOST_kButtonMaskLeft
@ GHOST_kButtonMaskButton5
@ GHOST_kButtonMaskMiddle
GHOST_TConsoleWindowState
@ GHOST_kConsoleWindowStateShow
@ GHOST_kConsoleWindowStateHideForNonConsoleLaunch
@ GHOST_kConsoleWindowStateHide
@ GHOST_kConsoleWindowStateToggle
GHOST_TTabletAPI
@ GHOST_kTabletWinPointer
@ GHOST_kTabletWintab
GHOST_DialogOptions
Definition GHOST_Types.h:73
@ GHOST_DialogError
Definition GHOST_Types.h:75
@ GHOST_DialogWarning
Definition GHOST_Types.h:74
#define GET_POINTERID_WPARAM(wParam)
@ MousePressed
@ MouseReleased
#define WINTAB_PRINTF(x,...)
#define USHORT
Definition GeoCommon.h:9
ImBuf * IMB_load_image_from_filepath(const char *filepath, const int flags, char r_colorspace[IM_MAX_SPACE]=nullptr)
Definition readimage.cc:204
ImBuf * IMB_load_image_from_memory(const unsigned char *mem, const size_t size, const int flags, const char *descr, const char *filepath=nullptr, char r_colorspace[IM_MAX_SPACE]=nullptr)
Definition readimage.cc:116
ImBuf * IMB_allocFromBuffer(const uint8_t *byte_buffer, const float *float_buffer, unsigned int w, unsigned int h, unsigned int channels)
void IMB_freeImBuf(ImBuf *ibuf)
bool IMB_save_image(ImBuf *ibuf, const char *filepath, const int flags)
Definition writeimage.cc:20
@ IMB_FTYPE_PNG
@ IB_byte_data
@ IB_multilayer
@ IB_mem
@ IB_test
BMesh const char void * data
long long int int64_t
unsigned long long int uint64_t
static btDbvtVolume bounds(btDbvtNode **leaves, int count)
Definition btDbvt.cpp:299
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
Definition btQuadWord.h:119
SIMD_FORCE_INLINE btScalar triple(const btVector3 &v1, const btVector3 &v2) const
Definition btVector3.h:419
void removeTypeEvents(GHOST_TEventType type, const GHOST_IWindow *window=nullptr)
static GHOST_ISystem * getSystem()
GHOST_TKey hardKey(RAWINPUT const &raw, bool *r_key_down)
void setTabletAPI(GHOST_TTabletAPI api) override
GHOST_TSuccess putClipboardImage(uint *rgba, int width, int height) const override
static GHOST_EventButton * processButtonEvent(GHOST_TEventType type, GHOST_WindowWin32 *window, GHOST_TButton mask)
GHOST_TKey processSpecialKey(short vKey, short scanCode) const
static GHOST_Event * processWindowSizeEvent(GHOST_WindowWin32 *window)
GHOST_TKey convertKey(short vKey, short ScanCode, short extend) const
GHOST_TSuccess setCursorPosition(int32_t x, int32_t y) override
GHOST_TSuccess showMessageBox(const char *title, const char *message, const char *help_label, const char *continue_label, const char *link, GHOST_DialogOptions dialog_options) const override
static void processWheelEventHorizontal(GHOST_WindowWin32 *window, WPARAM wParam, LPARAM lParam)
static GHOST_EventCursor * processCursorEvent(GHOST_WindowWin32 *window, const int32_t screen_co[2])
void getMainDisplayDimensions(uint32_t &width, uint32_t &height) const override
static GHOST_ContextD3D * createOffscreenContextD3D()
GHOST_IContext * createOffscreenContext(GHOST_GPUSettings gpuSettings) override
static GHOST_TSuccess disposeContextD3D(GHOST_ContextD3D *context)
GHOST_TSuccess hasClipboardImage() const override
static void processWintabEvent(GHOST_WindowWin32 *window)
uint64_t performanceCounterToMillis(__int64 perf_ticks) const
uint * getClipboardImage(int *r_width, int *r_height) const override
GHOST_TCapabilityFlag getCapabilities() const override
static GHOST_EventKey * processKeyEvent(GHOST_WindowWin32 *window, RAWINPUT const &raw)
void initDebug(GHOST_Debug debug) override
char * getClipboard(bool selection) const override
GHOST_TSuccess getCursorPosition(int32_t &x, int32_t &y) const override
uint8_t getNumDisplays() const override
GHOST_TSuccess getModifierKeys(GHOST_ModifierKeys &keys) const override
static void processMinMaxInfo(MINMAXINFO *minmax)
static void processWheelEventVertical(GHOST_WindowWin32 *window, WPARAM wParam, LPARAM lParam)
void putClipboard(const char *buffer, bool selection) const override
static GHOST_Event * processWindowEvent(GHOST_TEventType type, GHOST_WindowWin32 *window)
void getAllDisplayDimensions(uint32_t &width, uint32_t &height) const override
GHOST_TSuccess init() override
bool setConsoleWindowState(GHOST_TConsoleWindowState action) override
GHOST_IWindow * createWindow(const char *title, int32_t left, int32_t top, uint32_t width, uint32_t height, GHOST_TWindowState state, GHOST_GPUSettings gpuSettings, const bool exclusive=false, const bool is_dialog=false, const GHOST_IWindow *parentWindow=nullptr) override
GHOST_TSuccess exit() override
bool processEvents(bool waitForEvent) override
static void processPointerEvent(UINT type, GHOST_WindowWin32 *window, WPARAM wParam, LPARAM lParam, bool &eventhandled)
uint64_t getMilliSeconds() const override
GHOST_IWindow * getWindowUnderCursor(int32_t, int32_t) override
GHOST_TSuccess getPixelAtCursor(float r_color[3]) const override
static LRESULT WINAPI s_wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
static GHOST_TSuccess pushDragDropEvent(GHOST_TEventType eventType, GHOST_TDragnDropTypes draggedObjectType, GHOST_WindowWin32 *window, int mouseX, int mouseY, void *data)
GHOST_TSuccess getButtons(GHOST_Buttons &buttons) const override
GHOST_TSuccess disposeContext(GHOST_IContext *context) override
GHOST_EventManager * getEventManager() const
GHOST_WindowManager * getWindowManager() const
GHOST_TSuccess pushEvent(const GHOST_IEvent *event)
bool m_multitouchGestures
GHOST_TSuccess init() override
GHOST_TimerManager * getTimerManager() const
void initDebug(GHOST_Debug debug) override
void setTabletAPI(GHOST_TTabletAPI api) override
GHOST_WindowManager * m_windowManager
GHOST_EventManager * m_eventManager
GHOST_TSuccess exit() override
void dispatchEvents() override
bool fireTimers(uint64_t time)
const std::vector< GHOST_IWindow * > & getWindows() const
GHOST_IWindow * getActiveWindow() const
GHOST_TSuccess setActiveWindow(GHOST_IWindow *window)
void setWindowInactive(const GHOST_IWindow *window)
GHOST_TTrackpadInfo getTrackpadInfo()
void loadCursor(bool visible, GHOST_TStandardCursor cursorShape) const
bool usingTabletAPI(GHOST_TTabletAPI api) const
GHOST_TSuccess getPointerInfo(std::vector< GHOST_PointerInfoWin32 > &outPointerInfo, WPARAM wParam, LPARAM lParam)
void loadWintab(bool enable)
GHOST_TabletData getTabletData()
GHOST_Wintab * getWintab() const
void updateMouseCapture(GHOST_MouseCaptureEventWin32 event)
GHOST_TSuccess setState(GHOST_TWindowState state)
void getClientBounds(GHOST_Rect &bounds) const
void onPointerHitTest(WPARAM wParam)
void clientToScreen(int32_t inX, int32_t inY, int32_t &outX, int32_t &outY) const
GHOST_TWindowState getState() const
GHOST_TSuccess getCursorGrabBounds(GHOST_Rect &bounds) const override
void setCursorGrabAccum(int32_t x, int32_t y)
GHOST_TAxisFlag getCursorGrabAxis() const
GHOST_TStandardCursor getCursorShape() const override
bool getCursorVisibility() const override
GHOST_TGrabCursorMode getCursorGrabMode() const
bool getCursorGrabModeIsWarp() const
void getCursorGrabAccum(int32_t &x, int32_t &y) const
void mapWintabToSysCoordinates(int x_in, int y_in, int &x_out, int &y_out)
GHOST_TabletData getLastTabletData()
void getInput(std::vector< GHOST_WintabInfoWin32 > &outWintabInfo)
void processInfoChange(LPARAM lParam)
void updateCursorInfo()
void remapCoordinates()
bool trustCoordinates()
bool testCoordinates(int sysX, int sysY, int wtX, int wtY)
static void setDebug(bool debug)
#define UINT32_MAX
uint pos
blender::gpu::Batch * quad
uint top
#define abs
int count
format
ccl_device_inline float2 mask(const MaskType mask, const float2 a)
static ulong * next
static ulong state[N]
static int left
#define L
void set(GHOST_TButton mask, bool down)
GHOST_TDrawingContextType context_type
GHOST_GPUDevice preferred_device
void set(GHOST_TModifierKey mask, bool down)
GHOST_TTabletMode Active
ImbFormatOptions foptions
ImBufByteBuffer byte_buffer
enum eImbFileType ftype
unsigned int encoded_buffer_size
ImBufByteBuffer encoded_buffer
i
Definition text_draw.cc:230
wchar_t * alloc_utf16_from_8(const char *in8, size_t add)
Definition utfconv.cc:292
char * alloc_utf_8_from_16(const wchar_t *in16, size_t add)
Definition utfconv.cc:280
int conv_utf_8_to_16(const char *in8, wchar_t *out16, size_t size16)
Definition utfconv.cc:182
int conv_utf_16_to_8(const wchar_t *in16, char *out8, size_t size8)
Definition utfconv.cc:116
size_t count_utf_16_from_8(const char *string8)
Definition utfconv.cc:58
#define UTF16_ENCODE(in8str)
Definition utfconv.hh:80
#define UTF16_UN_ENCODE(in8str)
Definition utfconv.hh:84
uint len