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