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