1 // Written in the D programming language.
2 
3 /**
4 
5 This module contains implementation of Win32 platform support
6 
7 Provides Win32Window and Win32Platform classes.
8 
9 Usually you don't need to use this module directly.
10 
11 
12 Synopsis:
13 
14 ----
15 import dlangui.platforms.windows.winapp;
16 ----
17 
18 Copyright: Vadim Lopatin, 2014
19 License:   Boost License 1.0
20 Authors:   Vadim Lopatin, coolreader.org@gmail.com
21 */
22 module dlangui.platforms.windows.winapp;
23 
24 public import dlangui.core.config;
25 
26 static if (BACKEND_WIN32):
27 
28 import core.runtime;
29 import core.sys.windows.windows;
30 import std.string;
31 import std.utf;
32 import std.stdio;
33 import std.algorithm;
34 import std.file;
35 import dlangui.platforms.common.platform;
36 import dlangui.platforms.windows.win32fonts;
37 import dlangui.platforms.windows.win32drawbuf;
38 import dlangui.widgets.styles;
39 import dlangui.widgets.widget;
40 import dlangui.graphics.drawbuf;
41 import dlangui.graphics.images;
42 import dlangui.graphics.fonts;
43 import dlangui.core.logger;
44 import dlangui.core.files;
45 
46 static if (ENABLE_OPENGL) {
47     import dlangui.graphics.glsupport;
48 }
49 
50 // specify debug=DebugMouseEvents for logging mouse handling
51 // specify debug=DebugRedraw for logging drawing and layouts handling
52 // specify debug=DebugKeys for logging of key events
53 
54 pragma(lib, "gdi32.lib");
55 pragma(lib, "user32.lib");
56 
57 /// this function should be defined in user application!
58 extern (C) int UIAppMain(string[] args);
59 
60 immutable WIN_CLASS_NAME = "DLANGUI_APP";
61 
62 __gshared HINSTANCE _hInstance;
63 __gshared int _cmdShow;
64 
65 static if (ENABLE_OPENGL) {
66     bool setupPixelFormat(HDC hDC)
67     {
68         PIXELFORMATDESCRIPTOR pfd = {
69             PIXELFORMATDESCRIPTOR.sizeof,  /* size */
70             1,                              /* version */
71             PFD_SUPPORT_OPENGL |
72                 PFD_DRAW_TO_WINDOW |
73                 PFD_DOUBLEBUFFER,               /* support double-buffering */
74             PFD_TYPE_RGBA,                  /* color type */
75             16,                             /* prefered color depth */
76             0, 0, 0, 0, 0, 0,               /* color bits (ignored) */
77             0,                              /* no alpha buffer */
78             0,                              /* alpha bits (ignored) */
79             0,                              /* no accumulation buffer */
80             0, 0, 0, 0,                     /* accum bits (ignored) */
81             16,                             /* depth buffer */
82             0,                              /* no stencil buffer */
83             0,                              /* no auxiliary buffers */
84             0,                              /* main layer PFD_MAIN_PLANE */
85             0,                              /* reserved */
86             0, 0, 0,                        /* no layer, visible, damage masks */
87         };
88         int pixelFormat;
89 
90         pixelFormat = ChoosePixelFormat(hDC, &pfd);
91         if (pixelFormat == 0) {
92             Log.e("ChoosePixelFormat failed.");
93             return false;
94         }
95 
96         if (SetPixelFormat(hDC, pixelFormat, &pfd) != TRUE) {
97             Log.e("SetPixelFormat failed.");
98             return false;
99         }
100         return true;
101     }
102 
103     HPALETTE setupPalette(HDC hDC)
104     {
105         import core.stdc.stdlib;
106         HPALETTE hPalette = NULL;
107         int pixelFormat = GetPixelFormat(hDC);
108         PIXELFORMATDESCRIPTOR pfd;
109         LOGPALETTE* pPal;
110         int paletteSize;
111 
112         DescribePixelFormat(hDC, pixelFormat, PIXELFORMATDESCRIPTOR.sizeof, &pfd);
113 
114         if (pfd.dwFlags & PFD_NEED_PALETTE) {
115             paletteSize = 1 << pfd.cColorBits;
116         } else {
117             return null;
118         }
119 
120         pPal = cast(LOGPALETTE*)
121             malloc(LOGPALETTE.sizeof + paletteSize * PALETTEENTRY.sizeof);
122         pPal.palVersion = 0x300;
123         pPal.palNumEntries = cast(ushort)paletteSize;
124 
125         /* build a simple RGB color palette */
126         {
127             int redMask = (1 << pfd.cRedBits) - 1;
128             int greenMask = (1 << pfd.cGreenBits) - 1;
129             int blueMask = (1 << pfd.cBlueBits) - 1;
130             int i;
131 
132             for (i=0; i<paletteSize; ++i) {
133                 pPal.palPalEntry[i].peRed = cast(ubyte)(
134                     (((i >> pfd.cRedShift) & redMask) * 255) / redMask);
135                 pPal.palPalEntry[i].peGreen = cast(ubyte)(
136                     (((i >> pfd.cGreenShift) & greenMask) * 255) / greenMask);
137                 pPal.palPalEntry[i].peBlue = cast(ubyte)(
138                     (((i >> pfd.cBlueShift) & blueMask) * 255) / blueMask);
139                 pPal.palPalEntry[i].peFlags = 0;
140             }
141         }
142 
143         hPalette = CreatePalette(pPal);
144         free(pPal);
145 
146         if (hPalette) {
147             SelectPalette(hDC, hPalette, FALSE);
148             RealizePalette(hDC);
149         }
150 
151         return hPalette;
152     }
153 
154     private __gshared bool DERELICT_GL3_RELOADED = false;
155 }
156 
157 const uint CUSTOM_MESSAGE_ID = WM_USER + 1;
158 
159 static if (ENABLE_OPENGL) {
160 
161     /// Shared opengl context helper
162     struct SharedGLContext {
163         import bindbc.opengl;
164 
165         HGLRC _hGLRC; // opengl context
166         HPALETTE _hPalette;
167         bool _error;
168         /// Init OpenGL context, if not yet initialized
169         bool init(HDC hDC) {
170             if (_hGLRC) {
171                 // just setup pixel format
172                 if (setupPixelFormat(hDC)) {
173                     Log.i("OpenGL context already exists. Setting pixel format.");
174                 } else {
175                     Log.e("Cannot setup pixel format");
176                 }
177                 return true;
178             }
179             if (_error)
180                 return false;
181             if (setupPixelFormat(hDC)) {
182                 _hPalette = setupPalette(hDC);
183                 _hGLRC = wglCreateContext(hDC);
184                 if (_hGLRC) {
185                     bind(hDC);
186                     bool initialized = initGLSupport(Platform.instance.GLVersionMajor < 3);
187                     unbind(hDC);
188                     if (!initialized) {
189                         uninit();
190                         Log.e("Failed to init OpenGL shaders");
191                         _error = true;
192                         return false;
193                     }
194                     return true;
195                 } else {
196                     _error = true;
197                     return false;
198                 }
199             } else {
200                 Log.e("Cannot setup pixel format");
201                 _error = true;
202                 return false;
203             }
204         }
205         void uninit() {
206             if (_hGLRC) {
207                 wglDeleteContext(_hGLRC);
208                 _hGLRC = null;
209             }
210         }
211         /// make this context current for DC
212         void bind(HDC hDC) {
213             if (!wglMakeCurrent(hDC, _hGLRC)) {
214                 import std.string : format;
215                 Log.e("wglMakeCurrent is failed. GetLastError=%x".format(GetLastError()));
216             }
217         }
218         /// make null context current for DC
219         void unbind(HDC hDC) {
220             //wglMakeCurrent(hDC, null);
221             wglMakeCurrent(null, null);
222         }
223         void swapBuffers(HDC hDC) {
224             SwapBuffers(hDC);
225         }
226     }
227 
228     /// OpenGL context to share between windows
229     __gshared SharedGLContext sharedGLContext;
230 }
231 
232 interface UnknownWindowMessageHandler {
233     /// return true if message is handled, put return value into result
234     bool onUnknownWindowMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, ref LRESULT result);
235 }
236 
237 class Win32Window : Window {
238     Win32Platform _platform;
239 
240     HWND _hwnd;
241     dstring _caption;
242     Win32ColorDrawBuf _drawbuf;
243     private Win32Window _w32parent;
244     bool useOpengl;
245 
246     /// win32 only - return window handle
247     @property HWND windowHandle() {
248         return _hwnd;
249     }
250 
251     this(Win32Platform platform, dstring windowCaption, Window parent, uint flags, uint width = 0, uint height = 0) {
252         _w32parent = cast(Win32Window)parent;
253         HWND parenthwnd = _w32parent ? _w32parent._hwnd : null;
254         _dx = width;
255         _dy = height;
256         if (!_dx)
257             _dx = 600;
258         if (!_dy)
259             _dy = 400;
260         _platform = platform;
261         _caption = windowCaption;
262         _windowState = WindowState.hidden;
263         _flags = flags;
264         uint ws = WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
265         if (flags & WindowFlag.Resizable)
266             ws |= WS_OVERLAPPEDWINDOW;
267         else
268             ws |= WS_OVERLAPPED | WS_CAPTION | WS_CAPTION | WS_BORDER | WS_SYSMENU;
269         //if (flags & WindowFlag.Fullscreen)
270         //    ws |= SDL_WINDOW_FULLSCREEN;
271         Rect screenRc = getScreenDimensions();
272         Log.d("Screen dimensions: ", screenRc);
273 
274         int x = CW_USEDEFAULT;
275         int y = CW_USEDEFAULT;
276 
277         if (flags & WindowFlag.Fullscreen) {
278             // fullscreen
279             x = screenRc.left;
280             y = screenRc.top;
281             _dx = screenRc.width;
282             _dy = screenRc.height;
283             ws = WS_POPUP;
284         }
285         if (flags & WindowFlag.Borderless) {
286             ws = WS_POPUP | WS_SYSMENU;
287         }
288 
289 
290         _hwnd = CreateWindowW(toUTF16z(WIN_CLASS_NAME),      // window class name
291                             toUTF16z(windowCaption),  // window caption
292                             ws,  // window style
293                             x,        // initial x position
294                             y,        // initial y position
295                             _dx,        // initial x size
296                             _dy,        // initial y size
297                             parenthwnd,                 // parent window handle
298                             null,                 // window menu handle
299                             _hInstance,           // program instance handle
300                             cast(void*)this);                // creation parameters
301         static if (ENABLE_OPENGL) {
302             /* initialize OpenGL rendering */
303             HDC hDC = GetDC(_hwnd);
304 
305             if (openglEnabled) {
306                 useOpengl = sharedGLContext.init(hDC);
307             }
308         }
309 
310         RECT rect;
311         GetWindowRect(_hwnd, &rect);
312         handleWindowStateChange(WindowState.unspecified, Rect(rect.left, rect.top, _dx, _dy));
313 
314         if (platform.defaultWindowIcon.length != 0)
315             windowIcon = drawableCache.getImage(platform.defaultWindowIcon);
316     }
317 
318     static if (ENABLE_OPENGL) {
319         private void paintUsingOpenGL() {
320             // hack to stop infinite WM_PAINT loop
321             PAINTSTRUCT ps;
322             HDC hdc2 = BeginPaint(_hwnd, &ps);
323             EndPaint(_hwnd, &ps);
324 
325 
326             import dlangui.graphics.gldrawbuf;
327             //Log.d("onPaint() start drawing opengl viewport: ", _dx, "x", _dy);
328             //PAINTSTRUCT ps;
329             //HDC hdc = BeginPaint(_hwnd, &ps);
330             //scope(exit) EndPaint(_hwnd, &ps);
331             HDC hdc = GetDC(_hwnd);
332             sharedGLContext.bind(hdc);
333             //_glSupport = _gl;
334             glDisable(GL_DEPTH_TEST);
335             glViewport(0, 0, _dx, _dy);
336             float a = 1.0f;
337             float r = ((_backgroundColor >> 16) & 255) / 255.0f;
338             float g = ((_backgroundColor >> 8) & 255) / 255.0f;
339             float b = ((_backgroundColor >> 0) & 255) / 255.0f;
340             glClearColor(r, g, b, a);
341             glClear(GL_COLOR_BUFFER_BIT);
342 
343             GLDrawBuf buf = new GLDrawBuf(_dx, _dy, false);
344             buf.beforeDrawing();
345             static if (false) {
346                 // for testing for render
347                 buf.fillRect(Rect(100, 100, 200, 200), 0x704020);
348                 buf.fillRect(Rect(40, 70, 100, 120), 0x000000);
349                 buf.fillRect(Rect(80, 80, 150, 150), 0x80008000); // green
350                 drawableCache.get("exit").drawTo(buf, Rect(300, 100, 364, 164));
351                 drawableCache.get("btn_default_pressed").drawTo(buf, Rect(300, 200, 564, 264));
352                 drawableCache.get("btn_default_normal").drawTo(buf, Rect(300, 0, 400, 50));
353                 drawableCache.get("btn_default_selected").drawTo(buf, Rect(0, 0, 100, 50));
354                 FontRef fnt = currentTheme.font;
355                 fnt.drawText(buf, 40, 40, "Some Text 1234567890 !@#$^*", 0x80FF0000);
356             } else {
357                 onDraw(buf);
358             }
359             buf.afterDrawing();
360             sharedGLContext.swapBuffers(hdc);
361             //sharedGLContext.unbind(hdc);
362             destroy(buf);
363         }
364     }
365 
366     protected Rect getScreenDimensions() {
367         MONITORINFO monitor_info;
368         monitor_info.cbSize = monitor_info.sizeof;
369         HMONITOR hMonitor;
370         if (_hwnd) {
371             hMonitor = MonitorFromWindow(_hwnd, MONITOR_DEFAULTTONEAREST);
372         } else {
373             hMonitor = MonitorFromPoint(POINT(0,0), MONITOR_DEFAULTTOPRIMARY);
374         }
375         GetMonitorInfo(hMonitor,
376                        &monitor_info);
377         Rect res;
378         res.left = monitor_info.rcMonitor.left;
379         res.top = monitor_info.rcMonitor.top;
380         res.right = monitor_info.rcMonitor.right;
381         res.bottom = monitor_info.rcMonitor.bottom;
382         return res;
383     }
384 
385     protected bool _destroying;
386     ~this() {
387         debug Log.d("Window destructor");
388         _destroying = true;
389         if (_drawbuf) {
390             destroy(_drawbuf);
391             _drawbuf = null;
392         }
393 
394         /*
395         static if (ENABLE_OPENGL) {
396             import derelict.opengl3.wgl;
397             if (_hGLRC) {
398                 //glSupport.uninitShaders();
399                 //destroy(_glSupport);
400                 //_glSupport = null;
401                 //_gl = null;
402                 wglMakeCurrent (null, null) ;
403                 wglDeleteContext(_hGLRC);
404                 _hGLRC = null;
405             }
406         }
407         */
408         if (_hwnd)
409             DestroyWindow(_hwnd);
410         _hwnd = null;
411     }
412 
413     /// post event to handle in UI thread (this method can be used from background thread)
414     override void postEvent(CustomEvent event) {
415         super.postEvent(event);
416         PostMessageW(_hwnd, CUSTOM_MESSAGE_ID, 0, event.uniqueId);
417     }
418 
419     /// set handler for files dropped to app window
420     override @property Window onFilesDropped(void delegate(string[]) handler) {
421         super.onFilesDropped(handler);
422         DragAcceptFiles(_hwnd, handler ? TRUE : FALSE);
423         return this;
424     }
425 
426     private long _nextExpectedTimerTs;
427     private UINT_PTR _timerId = 1;
428 
429     /// schedule timer for interval in milliseconds - call window.onTimer when finished
430     override protected void scheduleSystemTimer(long intervalMillis) {
431         if (intervalMillis < 10)
432             intervalMillis = 10;
433         long nextts = currentTimeMillis + intervalMillis;
434         if (_timerId && _nextExpectedTimerTs && _nextExpectedTimerTs < nextts + 10)
435             return; // don't reschedule timer, timer event will be received soon
436         if (_hwnd) {
437             //_timerId =
438             SetTimer(_hwnd, _timerId, cast(uint)intervalMillis, null);
439             _nextExpectedTimerTs = nextts;
440         }
441     }
442 
443     void handleTimer(UINT_PTR timerId) {
444         //Log.d("handleTimer id=", timerId);
445         if (timerId == _timerId) {
446             KillTimer(_hwnd, timerId);
447             //_timerId = 0;
448             _nextExpectedTimerTs = 0;
449             onTimer();
450         }
451     }
452 
453     /// custom window message handler
454     Signal!UnknownWindowMessageHandler onUnknownWindowMessage;
455     private LRESULT handleUnknownWindowMessage(UINT message, WPARAM wParam, LPARAM lParam) {
456         if (onUnknownWindowMessage.assigned) {
457             LRESULT res;
458             if (onUnknownWindowMessage(_hwnd, message, wParam, lParam, res))
459                 return res;
460         }
461         return DefWindowProc(_hwnd, message, wParam, lParam);
462     }
463 
464     Win32ColorDrawBuf getDrawBuf() {
465         //RECT rect;
466         //GetClientRect(_hwnd, &rect);
467         //int dx = rect.right - rect.left;
468         //int dy = rect.bottom - rect.top;
469         if (_drawbuf is null)
470             _drawbuf = new Win32ColorDrawBuf(_dx, _dy);
471         else
472             _drawbuf.resize(_dx, _dy);
473         _drawbuf.resetClipping();
474         return _drawbuf;
475     }
476     override void show() {
477         if (!_mainWidget) {
478             Log.e("Window is shown without main widget");
479             _mainWidget = new Widget();
480         }
481         ReleaseCapture();
482         if (_mainWidget) {
483             _mainWidget.measure(SIZE_UNSPECIFIED, SIZE_UNSPECIFIED);
484             if (flags & WindowFlag.MeasureSize)
485                 resizeWindow(Point(_mainWidget.measuredWidth, _mainWidget.measuredHeight));
486             else
487                 adjustWindowOrContentSize(_mainWidget.measuredWidth, _mainWidget.measuredHeight);
488         }
489 
490         adjustPositionDuringShow();
491 
492         if (_flags & WindowFlag.Fullscreen) {
493             Rect rc = getScreenDimensions();
494             SetWindowPos(_hwnd, HWND_TOPMOST, 0, 0, rc.width, rc.height, SWP_SHOWWINDOW);
495             _windowState = WindowState.fullscreen;
496         } else {
497             ShowWindow(_hwnd, SW_SHOWNORMAL);
498             _windowState = WindowState.normal;
499         }
500         if (_mainWidget)
501             _mainWidget.setFocus();
502         SetFocus(_hwnd);
503         //UpdateWindow(_hwnd);
504     }
505 
506     override @property Window parentWindow() {
507         return _w32parent;
508     }
509 
510     override protected void handleWindowActivityChange(bool isWindowActive) {
511         super.handleWindowActivityChange(isWindowActive);
512     }
513 
514     override @property bool isActive() {
515         return _hwnd == GetForegroundWindow();
516     }
517 
518     override @property dstring windowCaption() const {
519         return _caption;
520     }
521 
522     override @property void windowCaption(dstring caption) {
523         _caption = caption;
524         if (_hwnd) {
525             Log.d("windowCaption ", caption);
526             SetWindowTextW(_hwnd, toUTF16z(_caption));
527         }
528     }
529 
530     /// change window state, position, or size; returns true if successful, false if not supported by platform
531     override bool setWindowState(WindowState newState, bool activate = false, Rect newWindowRect = RECT_VALUE_IS_NOT_SET) {
532         if (!_hwnd)
533             return false;
534         bool res = false;
535         // change state and activate support
536         switch(newState) {
537             case WindowState.unspecified:
538                 if (activate) {
539                     switch (_windowState) {
540                         case WindowState.hidden:
541                             // show hidden window
542                             ShowWindow(_hwnd, SW_SHOW);
543                             res = true;
544                             break;
545                         case WindowState.normal:
546                             ShowWindow(_hwnd, SW_SHOWNORMAL);
547                             res = true;
548                             break;
549                         case WindowState.fullscreen:
550                             ShowWindow(_hwnd, SW_SHOWNORMAL);
551                             res = true;
552                             break;
553                         case WindowState.minimized:
554                             ShowWindow(_hwnd, SW_SHOWMINIMIZED);
555                             res = true;
556                             break;
557                         case WindowState.maximized:
558                             ShowWindow(_hwnd, SW_SHOWMAXIMIZED);
559                             res = true;
560                             break;
561                         default:
562                             break;
563                     }
564                     res = true;
565                 }
566                 break;
567             case WindowState.maximized:
568                 if (_windowState != WindowState.maximized || activate) {
569                     ShowWindow(_hwnd, activate ? SW_SHOWMAXIMIZED : SW_MAXIMIZE);
570                     res = true;
571                 }
572                 break;
573             case WindowState.minimized:
574                 if (_windowState != WindowState.minimized || activate) {
575                     ShowWindow(_hwnd, activate ? SW_SHOWMINIMIZED : SW_MINIMIZE);
576                     res = true;
577                 }
578                 break;
579             case WindowState.hidden:
580                 if (_windowState != WindowState.hidden) {
581                     ShowWindow(_hwnd, SW_HIDE);
582                     res = true;
583                 }
584                 break;
585             case WindowState.normal:
586                 if (_windowState != WindowState.normal || activate) {
587                     ShowWindow(_hwnd, activate ? SW_SHOWNORMAL : SW_SHOWNA); // SW_RESTORE
588                     res = true;
589                 }
590                 break;
591 
592             default:
593                 break;
594         }
595         // change size and/or position
596         bool rectChanged = false;
597         if (newWindowRect != RECT_VALUE_IS_NOT_SET && (newState == WindowState.normal || newState == WindowState.unspecified)) {
598             UINT flags = SWP_NOOWNERZORDER | SWP_NOZORDER;
599             if (!activate)
600                 flags |= SWP_NOACTIVATE;
601             if (newWindowRect.top == int.min || newWindowRect.left == int.min) {
602                 // no position specified
603                 if (newWindowRect.bottom != int.min && newWindowRect.right != int.min) {
604                     // change size only
605                     SetWindowPos(_hwnd, NULL, 0, 0, newWindowRect.right + 2 * GetSystemMetrics(SM_CXDLGFRAME), newWindowRect.bottom + GetSystemMetrics(SM_CYCAPTION) + 2 * GetSystemMetrics(SM_CYDLGFRAME), flags | SWP_NOMOVE);
606                     rectChanged = true;
607                     res = true;
608                 }
609             } else {
610                 if (newWindowRect.bottom != int.min && newWindowRect.right != int.min) {
611                     // change size and position
612                     SetWindowPos(_hwnd, NULL, newWindowRect.left, newWindowRect.top, newWindowRect.right + 2 * GetSystemMetrics(SM_CXDLGFRAME), newWindowRect.bottom + GetSystemMetrics(SM_CYCAPTION) + 2 * GetSystemMetrics(SM_CYDLGFRAME), flags);
613                     rectChanged = true;
614                     res = true;
615                 } else {
616                     // change position only
617                     SetWindowPos(_hwnd, NULL, newWindowRect.left, newWindowRect.top, 0, 0, flags | SWP_NOSIZE);
618                     rectChanged = true;
619                     res = true;
620                 }
621             }
622         }
623 
624         if (rectChanged) {
625             handleWindowStateChange(newState, Rect(newWindowRect.left == int.min ? _windowRect.left : newWindowRect.left,
626                 newWindowRect.top == int.min ? _windowRect.top : newWindowRect.top, newWindowRect.right == int.min ? _windowRect.right : newWindowRect.right,
627                 newWindowRect.bottom == int.min ? _windowRect.bottom : newWindowRect.bottom));
628         }
629         else
630             handleWindowStateChange(newState, RECT_VALUE_IS_NOT_SET);
631 
632         return res;
633     }
634 
635     void onCreate() {
636         Log.d("Window onCreate");
637         _platform.onWindowCreated(_hwnd, this);
638     }
639     void onDestroy() {
640         Log.d("Window onDestroy");
641         _platform.onWindowDestroyed(_hwnd, this);
642     }
643 
644     protected bool _closeCalled;
645     /// close window
646     override void close() {
647         if (_closeCalled)
648             return;
649         _closeCalled = true;
650         Log.d("Window.close()");
651         _platform.closeWindow(this);
652     }
653 
654     override protected void handleWindowStateChange(WindowState newState, Rect newWindowRect = RECT_VALUE_IS_NOT_SET) {
655         if (_destroying)
656             return;
657         super.handleWindowStateChange(newState, newWindowRect);
658     }
659 
660     HICON _icon;
661 
662     uint _cursorType;
663 
664     HANDLE[ushort] _cursorCache;
665 
666     HANDLE loadCursor(ushort id) {
667         if (id in _cursorCache)
668             return _cursorCache[id];
669         HANDLE h = LoadCursor(null, MAKEINTRESOURCE(id));
670         _cursorCache[id] = h;
671         return h;
672     }
673 
674     void onSetCursorType() {
675         HANDLE winCursor = null;
676         switch (_cursorType) with(CursorType)
677         {
678             case None:
679                 winCursor = null;
680                 break;
681             case NotSet:
682                 break;
683             case Arrow:
684                 winCursor = loadCursor(IDC_ARROW);
685                 break;
686             case IBeam:
687                 winCursor = loadCursor(IDC_IBEAM);
688                 break;
689             case Wait:
690                 winCursor = loadCursor(IDC_WAIT);
691                 break;
692             case Crosshair:
693                 winCursor = loadCursor(IDC_CROSS);
694                 break;
695             case WaitArrow:
696                 winCursor = loadCursor(IDC_APPSTARTING);
697                 break;
698             case SizeNWSE:
699                 winCursor = loadCursor(IDC_SIZENWSE);
700                 break;
701             case SizeNESW:
702                 winCursor = loadCursor(IDC_SIZENESW);
703                 break;
704             case SizeWE:
705                 winCursor = loadCursor(IDC_SIZEWE);
706                 break;
707             case SizeNS:
708                 winCursor = loadCursor(IDC_SIZENS);
709                 break;
710             case SizeAll:
711                 winCursor = loadCursor(IDC_SIZEALL);
712                 break;
713             case No:
714                 winCursor = loadCursor(IDC_NO);
715                 break;
716             case Hand:
717                 winCursor = loadCursor(IDC_HAND);
718                 break;
719             default:
720                 break;
721         }
722         SetCursor(winCursor);
723     }
724 
725     /// sets cursor type for window
726     override protected void setCursorType(uint cursorType) {
727         // override to support different mouse cursors
728         _cursorType = cursorType;
729         onSetCursorType();
730     }
731 
732     /// sets window icon
733     @property override void windowIcon(DrawBufRef buf) {
734         if (_icon)
735             DestroyIcon(_icon);
736         _icon = null;
737         ColorDrawBuf icon = cast(ColorDrawBuf)buf.get;
738         if (!icon) {
739             Log.e("Trying to set null icon for window");
740             return;
741         }
742         Win32ColorDrawBuf resizedicon = new Win32ColorDrawBuf(icon, 32, 32);
743         resizedicon.invertAlpha();
744         ICONINFO ii;
745         HBITMAP mask = resizedicon.createTransparencyBitmap();
746         HBITMAP color = resizedicon.destroyLeavingBitmap();
747         ii.fIcon = TRUE;
748         ii.xHotspot = 0;
749         ii.yHotspot = 0;
750         ii.hbmMask = mask;
751         ii.hbmColor = color;
752         _icon = CreateIconIndirect(&ii);
753         if (_icon) {
754             SendMessageW(_hwnd, WM_SETICON, ICON_SMALL, cast(LPARAM)_icon);
755             SendMessageW(_hwnd, WM_SETICON, ICON_BIG, cast(LPARAM)_icon);
756         } else {
757             Log.e("failed to create icon");
758         }
759         if (mask)
760             DeleteObject(mask);
761         DeleteObject(color);
762     }
763 
764     private void paintUsingGDI() {
765         PAINTSTRUCT ps;
766         HDC hdc = BeginPaint(_hwnd, &ps);
767         scope(exit) EndPaint(_hwnd, &ps);
768 
769         Win32ColorDrawBuf buf = getDrawBuf();
770         buf.fill(_backgroundColor);
771         onDraw(buf);
772         buf.drawTo(hdc, 0, 0);
773     }
774 
775     void onPaint() {
776         debug(DebugRedraw) Log.d("onPaint()");
777         long paintStart = currentTimeMillis;
778         static if (ENABLE_OPENGL) {
779             if (useOpengl && sharedGLContext._hGLRC) {
780                 paintUsingOpenGL();
781             } else {
782                 paintUsingGDI();
783             }
784         } else {
785             paintUsingGDI();
786         }
787         long paintEnd = currentTimeMillis;
788         debug(DebugRedraw) Log.d("WM_PAINT handling took ", paintEnd - paintStart, " ms");
789     }
790 
791     protected ButtonDetails _lbutton;
792     protected ButtonDetails _mbutton;
793     protected ButtonDetails _rbutton;
794 
795     private void updateButtonsState(uint flags) {
796         if (!(flags & MK_LBUTTON) && _lbutton.isDown)
797             _lbutton.reset();
798         if (!(flags & MK_MBUTTON) && _mbutton.isDown)
799             _mbutton.reset();
800         if (!(flags & MK_RBUTTON) && _rbutton.isDown)
801             _rbutton.reset();
802     }
803 
804     private bool _mouseTracking;
805     private bool onMouse(uint message, uint flags, short x, short y) {
806         debug(DebugMouseEvents) Log.d("Win32 Mouse Message ", message, " flags=", flags, " x=", x, " y=", y);
807         MouseButton button = MouseButton.None;
808         MouseAction action = MouseAction.ButtonDown;
809         ButtonDetails * pbuttonDetails = null;
810         short wheelDelta = 0;
811         switch (message) {
812             case WM_MOUSEMOVE:
813                 action = MouseAction.Move;
814                 updateButtonsState(flags);
815                 break;
816             case WM_LBUTTONDOWN:
817                 action = MouseAction.ButtonDown;
818                 button = MouseButton.Left;
819                 pbuttonDetails = &_lbutton;
820                 SetFocus(_hwnd);
821                 break;
822             case WM_RBUTTONDOWN:
823                 action = MouseAction.ButtonDown;
824                 button = MouseButton.Right;
825                 pbuttonDetails = &_rbutton;
826                 SetFocus(_hwnd);
827                 break;
828             case WM_MBUTTONDOWN:
829                 action = MouseAction.ButtonDown;
830                 button = MouseButton.Middle;
831                 pbuttonDetails = &_mbutton;
832                 SetFocus(_hwnd);
833                 break;
834             case WM_LBUTTONUP:
835                 action = MouseAction.ButtonUp;
836                 button = MouseButton.Left;
837                 pbuttonDetails = &_lbutton;
838                 break;
839             case WM_RBUTTONUP:
840                 action = MouseAction.ButtonUp;
841                 button = MouseButton.Right;
842                 pbuttonDetails = &_rbutton;
843                 break;
844             case WM_MBUTTONUP:
845                 action = MouseAction.ButtonUp;
846                 button = MouseButton.Middle;
847                 pbuttonDetails = &_mbutton;
848                 break;
849             case WM_MOUSELEAVE:
850                 debug(DebugMouseEvents) Log.d("WM_MOUSELEAVE");
851                 action = MouseAction.Leave;
852                 break;
853             case WM_MOUSEWHEEL:
854                 {
855                     action = MouseAction.Wheel;
856                     wheelDelta = (cast(short)(flags >> 16)) / 120;
857                     POINT pt;
858                     pt.x = x;
859                     pt.y = y;
860                     ScreenToClient(_hwnd, &pt);
861                     x = cast(short)pt.x;
862                     y = cast(short)pt.y;
863                 }
864                 break;
865             default:
866                 // unsupported event
867                 return false;
868         }
869         if (action == MouseAction.ButtonDown) {
870             pbuttonDetails.down(x, y, cast(ushort)flags);
871         } else if (action == MouseAction.ButtonUp) {
872             pbuttonDetails.up(x, y, cast(ushort)flags);
873         }
874         if (((message == WM_MOUSELEAVE) || (x < 0 || y < 0 || x >= _dx || y >= _dy)) && _mouseTracking) {
875             if (!isMouseCaptured() || (!_lbutton.isDown && !_rbutton.isDown && !_mbutton.isDown)) {
876                 action = MouseAction.Leave;
877                 debug(DebugMouseEvents) Log.d("Win32Window.onMouse releasing capture");
878                 _mouseTracking = false;
879                 ReleaseCapture();
880             }
881         }
882         if (message != WM_MOUSELEAVE && !_mouseTracking) {
883             if (x >=0 && y >= 0 && x < _dx && y < _dy) {
884                 debug(DebugMouseEvents) Log.d("Win32Window.onMouse Setting capture");
885                 _mouseTracking = true;
886                 SetCapture(_hwnd);
887             }
888         }
889         MouseEvent event = new MouseEvent(action, button, cast(ushort)flags, x, y, wheelDelta);
890         event.lbutton = _lbutton;
891         event.rbutton = _rbutton;
892         event.mbutton = _mbutton;
893         bool res = dispatchMouseEvent(event);
894         if (res) {
895             //Log.v("Calling update() after mouse event");
896             update();
897         }
898         return res;
899     }
900 
901 
902     protected uint _keyFlags;
903 
904     protected void updateKeyFlags(KeyAction action, KeyFlag flag, uint preserveFlag) {
905         if (action == KeyAction.KeyDown)
906             _keyFlags |= flag;
907         else {
908             if (preserveFlag && (_keyFlags & preserveFlag) == preserveFlag) {
909                 // e.g. when both lctrl and rctrl are pressed, and lctrl is up, preserve rctrl flag
910                 _keyFlags = (_keyFlags & ~flag) | preserveFlag;
911             } else {
912                 _keyFlags &= ~flag;
913             }
914         }
915     }
916 
917     bool onKey(KeyAction action, uint keyCode, int repeatCount, dchar character = 0, bool syskey = false) {
918         debug(KeyInput) Log.d("enter onKey action=", action, " keyCode=", keyCode, " char=", character, "(", cast(int)character, ")", " syskey=", syskey, "    _keyFlags=", "%04x"d.format(_keyFlags));
919         KeyEvent event;
920         if (syskey)
921             _keyFlags |= KeyFlag.Alt;
922         //else
923         //    _keyFlags &= ~KeyFlag.Alt;
924         uint oldFlags = _keyFlags;
925         if (action == KeyAction.KeyDown || action == KeyAction.KeyUp) {
926             switch(keyCode) {
927                 case KeyCode.LSHIFT:
928                     updateKeyFlags(action, KeyFlag.LShift, KeyFlag.RShift);
929                     break;
930                 case KeyCode.RSHIFT:
931                     updateKeyFlags(action, KeyFlag.RShift, KeyFlag.LShift);
932                     break;
933                 case KeyCode.LCONTROL:
934                     updateKeyFlags(action, KeyFlag.LControl, KeyFlag.RControl);
935                     break;
936                 case KeyCode.RCONTROL:
937                     updateKeyFlags(action, KeyFlag.RControl, KeyFlag.LControl);
938                     break;
939                 case KeyCode.LALT:
940                     updateKeyFlags(action, KeyFlag.LAlt, KeyFlag.RAlt);
941                     break;
942                 case KeyCode.RALT:
943                     updateKeyFlags(action, KeyFlag.RAlt, KeyFlag.LAlt);
944                     break;
945                 case KeyCode.LWIN:
946                     updateKeyFlags(action, KeyFlag.LMenu, KeyFlag.RMenu);
947                     break;
948                 case KeyCode.RWIN:
949                     updateKeyFlags(action, KeyFlag.RMenu, KeyFlag.LMenu);
950                     break;
951                 //case KeyCode.WIN:
952                 case KeyCode.CONTROL:
953                 case KeyCode.SHIFT:
954                 case KeyCode.ALT:
955                 //case KeyCode.WIN:
956                     break;
957                 default:
958                     updateKeyFlags((GetKeyState(VK_LCONTROL) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.LControl, KeyFlag.RControl);
959                     updateKeyFlags((GetKeyState(VK_RCONTROL) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.RControl, KeyFlag.LControl);
960                     updateKeyFlags((GetKeyState(VK_LSHIFT) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.LShift, KeyFlag.RShift);
961                     updateKeyFlags((GetKeyState(VK_RSHIFT) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.RShift, KeyFlag.LShift);
962                     updateKeyFlags((GetKeyState(VK_LWIN) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.LMenu, KeyFlag.RMenu);
963                     updateKeyFlags((GetKeyState(VK_RWIN) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.RMenu, KeyFlag.LMenu);
964                     updateKeyFlags((GetKeyState(VK_LMENU) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.LAlt, KeyFlag.RAlt);
965                     updateKeyFlags((GetKeyState(VK_RMENU) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.RAlt, KeyFlag.LAlt);
966                     //updateKeyFlags((GetKeyState(VK_LALT) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.LAlt, KeyFlag.RAlt);
967                     //updateKeyFlags((GetKeyState(VK_RALT) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.RAlt, KeyFlag.LAlt);
968                     break;
969             }
970             //updateKeyFlags((GetKeyState(VK_CONTROL) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.Control);
971             //updateKeyFlags((GetKeyState(VK_SHIFT) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.Shift);
972             //updateKeyFlags((GetKeyState(VK_MENU) & 0x8000) != 0 ? KeyAction.KeyDown : KeyAction.KeyUp, KeyFlag.Alt);
973             if (keyCode == 0xBF)
974                 keyCode = KeyCode.KEY_DIVIDE;
975 
976             debug(KeyInput) {
977                 if (oldFlags != _keyFlags) {
978                     debug(KeyInput) Log.d(" flags updated: onKey action=", action, " keyCode=", keyCode, " char=", character, "(", cast(int)character, ")", " syskey=", syskey, "    _keyFlags=", "%04x"d.format(_keyFlags));
979                 }
980                 //if (action == KeyAction.KeyDown)
981                 //    Log.d("keydown, keyFlags=", _keyFlags);
982             }
983 
984             event = new KeyEvent(action, keyCode, _keyFlags);
985         } else if (action == KeyAction.Text && character != 0) {
986             bool ctrlAZKeyCode = (character >= 1 && character <= 26);
987             if ((_keyFlags & (KeyFlag.Control | KeyFlag.Alt)) && ctrlAZKeyCode) {
988                 event = new KeyEvent(action, KeyCode.KEY_A + character - 1, _keyFlags);
989             } else {
990                 dchar[] text;
991                 text ~= character;
992                 uint newFlags = _keyFlags;
993                 if ((newFlags & KeyFlag.Alt) && (newFlags & KeyFlag.Control)) {
994                     newFlags &= (~(KeyFlag.LRAlt)) & (~(KeyFlag.LRControl));
995                     debug(KeyInput) Log.d(" flags updated for text: onKey action=", action, " keyCode=", keyCode, " char=", character, "(", cast(int)character, ")", " syskey=", syskey, "    _keyFlags=", "%04x"d.format(_keyFlags));
996                 }
997                 event = new KeyEvent(action, 0, newFlags, cast(dstring)text);
998             }
999         }
1000         bool res = false;
1001         if (event !is null) {
1002             res = dispatchKeyEvent(event);
1003         }
1004         if (res) {
1005             debug(DebugRedraw) Log.d("Calling update() after key event");
1006             update();
1007         }
1008         return res;
1009     }
1010 
1011     /// request window redraw
1012     override void invalidate() {
1013         InvalidateRect(_hwnd, null, FALSE);
1014         //UpdateWindow(_hwnd);
1015     }
1016 
1017     /// after drawing, call to schedule redraw if animation is active
1018     override void scheduleAnimation() {
1019         invalidate();
1020     }
1021 
1022 }
1023 
1024 class Win32Platform : Platform {
1025     this() {
1026     }
1027     bool registerWndClass() {
1028         //MSG  msg;
1029         WNDCLASSW wndclass;
1030 
1031         wndclass.style         = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
1032         wndclass.lpfnWndProc   = cast(WNDPROC)&WndProc;
1033         wndclass.cbClsExtra    = 0;
1034         wndclass.cbWndExtra    = 0;
1035         wndclass.hInstance     = _hInstance;
1036         wndclass.hIcon         = LoadIcon(null, IDI_APPLICATION);
1037         wndclass.hCursor       = LoadCursor(null, IDC_ARROW);
1038         wndclass.hbrBackground = cast(HBRUSH)GetStockObject(WHITE_BRUSH);
1039         wndclass.lpszMenuName  = null;
1040         wndclass.lpszClassName = toUTF16z(WIN_CLASS_NAME);
1041 
1042         if(!RegisterClassW(&wndclass))
1043         {
1044             return false;
1045         }
1046         HDC dc = CreateCompatibleDC(NULL);
1047         SCREEN_DPI = GetDeviceCaps(dc, LOGPIXELSY);
1048         DeleteObject(dc);
1049 
1050         return true;
1051     }
1052     override int enterMessageLoop() {
1053         MSG  msg;
1054         while (GetMessage(&msg, null, 0, 0))
1055         {
1056             TranslateMessage(&msg);
1057             DispatchMessage(&msg);
1058             destroyClosedWindows();
1059         }
1060         return cast(int)msg.wParam;
1061     }
1062 
1063     private Win32Window[ulong] _windowMap;
1064     private Win32Window[] _windowList;
1065 
1066     /// add window to window map
1067     void onWindowCreated(HWND hwnd, Win32Window window) {
1068         Log.v("created window, adding to map");
1069         _windowMap[cast(ulong)hwnd] = window;
1070         _windowList ~= window;
1071     }
1072     /// remove window from window map, returns true if there are some more windows left in map
1073     bool onWindowDestroyed(HWND hwnd, Win32Window window) {
1074         Log.v("destroyed window, removing from map");
1075         Win32Window wnd = getWindow(hwnd);
1076         if (wnd) {
1077             _windowMap.remove(cast(ulong)hwnd);
1078             _windowsToDestroy ~= window;
1079             //destroy(window);
1080         }
1081         for (uint i = 0; i < _windowList.length; i++) {
1082             if (window is _windowList[i]) {
1083                 for (uint j = i; j + 1 < _windowList.length; j++)
1084                     _windowList[j] = _windowList[j + 1];
1085                 _windowList[$ - 1] = null;
1086                 _windowList.length--;
1087                 break;
1088             }
1089         }
1090         return _windowMap.length > 0;
1091     }
1092     /// returns number of currently active windows
1093     @property int windowCount() {
1094         return cast(int)_windowMap.length;
1095     }
1096     /// returns window instance by HWND
1097     Win32Window getWindow(HWND hwnd) {
1098         if ((cast(ulong)hwnd) in _windowMap)
1099             return _windowMap[cast(ulong)hwnd];
1100         return null;
1101     }
1102     override Window createWindow(dstring windowCaption, Window parent, uint flags = WindowFlag.Resizable, uint width = 0, uint height = 0) {
1103         Log.d("Platform.createWindow is called");
1104         width = pointsToPixels(width);
1105         height = pointsToPixels(height);
1106         Log.v("Platform.createWindow : setDefaultLanguageAndThemeIfNecessary");
1107         setDefaultLanguageAndThemeIfNecessary();
1108         Log.v("Platform.createWindow : new Win32Window");
1109         return new Win32Window(this, windowCaption, parent, flags, width, height);
1110     }
1111 
1112     /// calls request layout for all windows
1113     override void requestLayout() {
1114         foreach(w; _windowMap) {
1115             w.requestLayout();
1116             w.invalidate();
1117         }
1118     }
1119 
1120     /// returns true if there is some modal window opened above this window, and this window should not process mouse/key input and should not allow closing
1121     override bool hasModalWindowsAbove(Window w) {
1122         // override in platform specific class
1123         for (uint i = 0; i + 1 < _windowList.length; i++) {
1124             if (_windowList[i] is w) {
1125                 for (uint j = i + 1; j < _windowList.length; j++) {
1126                     if (_windowList[j].flags & WindowFlag.Modal && _windowList[j].windowState != WindowState.hidden)
1127                         return true;
1128                 }
1129                 return false;
1130             }
1131         }
1132         return false;
1133     }
1134 
1135     /// handle theme change: e.g. reload some themed resources
1136     override void onThemeChanged() {
1137         super.onThemeChanged();
1138         if (currentTheme)
1139             currentTheme.onThemeChanged();
1140         foreach(w; _windowMap)
1141             w.dispatchThemeChanged();
1142     }
1143 
1144     /// list of windows for deferred destroy in message loop
1145     Win32Window[] _windowsToDestroy;
1146 
1147     /// close window
1148     override void closeWindow(Window w) {
1149         Win32Window window = cast(Win32Window)w;
1150         _windowsToDestroy ~= window;
1151         SendMessage(window._hwnd, WM_CLOSE, 0, 0);
1152         //window
1153     }
1154 
1155     /// destroy window objects planned for destroy
1156     void destroyClosedWindows() {
1157         foreach(Window w; _windowsToDestroy) {
1158             destroy(w);
1159         }
1160         _windowsToDestroy.length = 0;
1161     }
1162 
1163     /// check has clipboard text
1164     override bool hasClipboardText(bool mouseBuffer = false) {
1165         if (mouseBuffer)
1166             return false;
1167         return (IsClipboardFormatAvailable(CF_UNICODETEXT) != 0);
1168     }
1169 
1170     /// retrieves text from clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux)
1171     override dstring getClipboardText(bool mouseBuffer = false) {
1172         dstring res = null;
1173         if (mouseBuffer)
1174             return res; // not supporetd under win32
1175         if (!IsClipboardFormatAvailable(CF_UNICODETEXT))
1176             return res;
1177         if (!OpenClipboard(NULL))
1178             return res;
1179 
1180         HGLOBAL hglb = GetClipboardData(CF_UNICODETEXT);
1181         if (hglb != NULL)
1182         {
1183             LPWSTR lptstr = cast(LPWSTR)GlobalLock(hglb);
1184             if (lptstr != NULL)
1185             {
1186                 wstring w = fromWStringz(lptstr);
1187                 res = normalizeEndOfLineCharacters(toUTF32(w));
1188 
1189                 GlobalUnlock(hglb);
1190             }
1191         }
1192 
1193         CloseClipboard();
1194         //Log.d("getClipboardText(", res, ")");
1195         return res;
1196     }
1197 
1198     /// sets text to clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux)
1199     override void setClipboardText(dstring text, bool mouseBuffer = false) {
1200         //Log.d("setClipboardText(", text, ")");
1201         if (text.length < 1 || mouseBuffer)
1202             return;
1203         if (!OpenClipboard(NULL))
1204             return;
1205         EmptyClipboard();
1206         wstring w = toUTF16(text);
1207         HGLOBAL hglbCopy = GlobalAlloc(GMEM_MOVEABLE,
1208                            cast(uint)((w.length + 1) * TCHAR.sizeof));
1209         if (hglbCopy == NULL) {
1210             CloseClipboard();
1211             return;
1212         }
1213         LPWSTR lptstrCopy = cast(LPWSTR)GlobalLock(hglbCopy);
1214         for (int i = 0; i < w.length; i++) {
1215             lptstrCopy[i] = w[i];
1216         }
1217         lptstrCopy[w.length] = 0;
1218         GlobalUnlock(hglbCopy);
1219         SetClipboardData(CF_UNICODETEXT, hglbCopy);
1220 
1221         CloseClipboard();
1222     }
1223 }
1224 
1225 extern(Windows)
1226 int DLANGUIWinMain(void* hInstance, void* hPrevInstance,
1227             char* lpCmdLine, int nCmdShow) {
1228     int result;
1229 
1230     try {
1231         Runtime.initialize();
1232 
1233         // call SetProcessDPIAware to support HI DPI - fix by Kapps
1234         auto ulib = LoadLibraryA("user32.dll");
1235         alias SetProcessDPIAwareFunc = int function();
1236         auto setDpiFunc = cast(SetProcessDPIAwareFunc)GetProcAddress(ulib, "SetProcessDPIAware");
1237         if(setDpiFunc) // Should never fail, but just in case...
1238             setDpiFunc();
1239 
1240         result = myWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
1241         // TODO: fix hanging on multithreading app
1242         Runtime.terminate();
1243     }
1244     catch (Throwable e) // catch any uncaught exceptions
1245     {
1246         MessageBoxW(null, toUTF16z(e.toString()), "Error",
1247                     MB_OK | MB_ICONEXCLAMATION);
1248         result = 0;     // failed
1249     }
1250 
1251     return result;
1252 }
1253 
1254 extern(Windows)
1255 int DLANGUIWinMainProfile(string[] args)
1256 {
1257     int result;
1258 
1259     try {
1260         // call SetProcessDPIAware to support HI DPI - fix by Kapps
1261         auto ulib = LoadLibraryA("user32.dll");
1262         alias SetProcessDPIAwareFunc = int function();
1263         auto setDpiFunc = cast(SetProcessDPIAwareFunc)GetProcAddress(ulib, "SetProcessDPIAware");
1264         if(setDpiFunc) // Should never fail, but just in case...
1265             setDpiFunc();
1266 
1267         result = myWinMainProfile(args);
1268     }
1269     catch (Throwable e) // catch any uncaught exceptions
1270     {
1271         MessageBoxW(null, toUTF16z(e.toString()), "Error",
1272                     MB_OK | MB_ICONEXCLAMATION);
1273         result = 0;     // failed
1274     }
1275 
1276     return result;
1277 }
1278 
1279 /// split command line arg list; prepend with executable file name
1280 string[] splitCmdLine(string line) {
1281     string[] res;
1282     res ~= exeFilename();
1283     int start = 0;
1284     bool insideQuotes = false;
1285     for (int i = 0; i <= line.length; i++) {
1286         char ch = i < line.length ? line[i] : 0;
1287         if (ch == '\"') {
1288             if (insideQuotes) {
1289                 if (i > start)
1290                     res ~= line[start .. i];
1291                 start = i + 1;
1292                 insideQuotes = false;
1293             } else {
1294                 insideQuotes = true;
1295                 start = i + 1;
1296             }
1297         } else if (!insideQuotes && (ch == ' ' || ch == '\t' || ch == 0)) {
1298             if (i > start) {
1299                 res ~= line[start .. i];
1300             }
1301             start = i + 1;
1302         }
1303     }
1304     return res;
1305 }
1306 
1307 private __gshared Win32Platform w32platform;
1308 
1309 int myWinMain(void* hInstance, void* hPrevInstance, char* lpCmdLine, int iCmdShow)
1310 {
1311     initLogs();
1312 
1313     Log.d("myWinMain()");
1314     string basePath = exePath();
1315     Log.i("Current executable: ", exePath());
1316     string cmdline = fromStringz(lpCmdLine).dup;
1317     Log.i("Command line: ", cmdline);
1318     string[] args = splitCmdLine(cmdline);
1319     Log.i("Command line params: ", args);
1320 
1321     _cmdShow = iCmdShow;
1322     _hInstance = hInstance;
1323 
1324     Log.v("Creating platform");
1325     w32platform = new Win32Platform();
1326     Log.v("Registering window class");
1327     if (!w32platform.registerWndClass()) {
1328         MessageBoxA(null, "This program requires Windows NT!", "DLANGUI App".toStringz, MB_ICONERROR);
1329         return 0;
1330     }
1331     Platform.setInstance(w32platform);
1332 
1333     DOUBLE_CLICK_THRESHOLD_MS = GetDoubleClickTime();
1334 
1335     Log.v("Initializing font manager");
1336     if (!initFontManager()) {
1337         Log.e("******************************************************************");
1338         Log.e("No font files found!!!");
1339         Log.e("Currently, only hardcoded font paths implemented.");
1340         Log.e("Probably you can modify sdlapp.d to add some fonts for your system.");
1341         Log.e("******************************************************************");
1342         assert(false);
1343     }
1344     initResourceManagers();
1345 
1346     currentTheme = createDefaultTheme();
1347 
1348     Log.i("Entering UIAppMain: ", args);
1349     int result = -1;
1350     try {
1351         result = UIAppMain(args);
1352         Log.i("UIAppMain returned ", result);
1353     } catch (Exception e) {
1354         Log.e("Abnormal UIAppMain termination");
1355         Log.e("UIAppMain exception: ", e);
1356     }
1357 
1358     releaseResourcesOnAppExit();
1359 
1360     Log.d("Exiting main");
1361     debug {
1362         APP_IS_SHUTTING_DOWN = true;
1363         import core.memory : GC;
1364         Log.d("Calling GC.collect");
1365         GC.collect();
1366         if (DrawBuf.instanceCount)
1367             Log.d("Non-zero DrawBuf instance count when exiting: ", DrawBuf.instanceCount);
1368     }
1369 
1370     return result;
1371 }
1372 
1373 int myWinMainProfile(string[] args)
1374 {
1375     initLogs();
1376 
1377     Log.d("myWinMain()");
1378     string basePath = exePath();
1379     Log.i("Current executable: ", exePath());
1380     Log.i("Command line params: ", args);
1381 
1382     _cmdShow = SW_SHOW;
1383     _hInstance = GetModuleHandle(NULL);
1384 
1385     Log.v("Creating platform");
1386     w32platform = new Win32Platform();
1387     Log.v("Registering window class");
1388     if (!w32platform.registerWndClass()) {
1389         MessageBoxA(null, "This program requires Windows NT!", "DLANGUI App".toStringz, MB_ICONERROR);
1390         return 0;
1391     }
1392     Platform.setInstance(w32platform);
1393 
1394     DOUBLE_CLICK_THRESHOLD_MS = GetDoubleClickTime();
1395 
1396     Log.v("Initializing font manager");
1397     if (!initFontManager()) {
1398         Log.e("******************************************************************");
1399         Log.e("No font files found!!!");
1400         Log.e("Currently, only hardcoded font paths implemented.");
1401         Log.e("Probably you can modify sdlapp.d to add some fonts for your system.");
1402         Log.e("******************************************************************");
1403         assert(false);
1404     }
1405     initResourceManagers();
1406 
1407     currentTheme = createDefaultTheme();
1408 
1409     Log.i("Entering UIAppMain: ", args);
1410     int result = -1;
1411     try {
1412         result = UIAppMain(args);
1413         Log.i("UIAppMain returned ", result);
1414     } catch (Exception e) {
1415         Log.e("Abnormal UIAppMain termination");
1416         Log.e("UIAppMain exception: ", e);
1417     }
1418 
1419     releaseResourcesOnAppExit();
1420 
1421     Log.d("Exiting main");
1422     debug {
1423         APP_IS_SHUTTING_DOWN = true;
1424         import core.memory : GC;
1425         Log.d("Calling GC.collect");
1426         GC.collect();
1427         if (DrawBuf.instanceCount)
1428             Log.d("Non-zero DrawBuf instance count when exiting: ", DrawBuf.instanceCount);
1429     }
1430 
1431     return result;
1432 }
1433 
1434 
1435 extern(Windows)
1436 LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
1437 {
1438     HDC hdc;
1439     RECT rect;
1440 
1441     void * p = cast(void*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
1442     Win32Window windowParam = p is null ? null : cast(Win32Window)(p);
1443     Win32Window window = w32platform.getWindow(hwnd);
1444     if (windowParam !is null && window !is null)
1445         assert(window is windowParam);
1446     if (window is null && windowParam !is null) {
1447         Log.e("Cannot find window in map by HWND");
1448     }
1449 
1450     switch (message)
1451     {
1452         case WM_CREATE:
1453             {
1454                 CREATESTRUCT * pcreateStruct = cast(CREATESTRUCT*)lParam;
1455                 window = cast(Win32Window)pcreateStruct.lpCreateParams;
1456                 void * ptr = cast(void*) window;
1457                 SetWindowLongPtr(hwnd, GWLP_USERDATA, cast(LONG_PTR)ptr);
1458                 window._hwnd = hwnd;
1459                 window.onCreate();
1460                 //window.handleUnknownWindowMessage(message, wParam, lParam);
1461             }
1462             return 0;
1463         case WM_DESTROY:
1464             if (window !is null) {
1465                 //window.handleUnknownWindowMessage(message, wParam, lParam);
1466                 window.onDestroy();
1467             }
1468             if (w32platform.windowCount == 0)
1469                 PostQuitMessage(0);
1470             return 0;
1471         case WM_WINDOWPOSCHANGED:
1472             {
1473                 if (window !is null) {
1474 
1475                     if (IsIconic(hwnd)) {
1476                         window.handleWindowStateChange(WindowState.minimized);
1477                     }
1478                     else {
1479                         WINDOWPOS * pos = cast(WINDOWPOS*)lParam;
1480                         //Log.d("WM_WINDOWPOSCHANGED: ", *pos);
1481 
1482                         GetClientRect(hwnd, &rect);
1483                         int dx = rect.right - rect.left;
1484                         int dy = rect.bottom - rect.top;
1485                         WindowState state = WindowState.unspecified;
1486                         if (IsZoomed(hwnd))
1487                             state = WindowState.maximized;
1488                         else if (IsIconic(hwnd))
1489                             state = WindowState.minimized;
1490                         else if (IsWindowVisible(hwnd))
1491                             state = WindowState.normal;
1492                         else
1493                             state = WindowState.hidden;
1494                         window.handleWindowStateChange(state,
1495                             Rect(pos.x, pos.y, dx, dy));
1496                         if (window.width != dx || window.height != dy) {
1497                             window.onResize(dx, dy);
1498                             InvalidateRect(hwnd, null, FALSE);
1499                         }
1500                     }
1501                 }
1502             }
1503             return 0;
1504         case WM_ACTIVATE:
1505             {
1506                 if (window) {
1507                     if (wParam == WA_INACTIVE)
1508                         window.handleWindowActivityChange(false);
1509                     else if (wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE)
1510                         window.handleWindowActivityChange(true);
1511                 }
1512             }
1513             return 0;
1514         case CUSTOM_MESSAGE_ID:
1515             if (window !is null) {
1516                 window.handlePostedEvent(cast(uint)lParam);
1517             }
1518             return 1;
1519         case WM_ERASEBKGND:
1520             // processed
1521             return 1;
1522         case WM_PAINT:
1523             {
1524                 if (window !is null)
1525                     window.onPaint();
1526             }
1527             return 0; // processed
1528         case WM_SETCURSOR:
1529             {
1530                 if (window !is null) {
1531                     if (LOWORD(lParam) == HTCLIENT) {
1532                         window.onSetCursorType();
1533                         return 1;
1534                     }
1535                 }
1536             }
1537             break;
1538         case WM_MOUSELEAVE:
1539         case WM_MOUSEMOVE:
1540         case WM_LBUTTONDOWN:
1541         case WM_MBUTTONDOWN:
1542         case WM_RBUTTONDOWN:
1543         case WM_LBUTTONUP:
1544         case WM_MBUTTONUP:
1545         case WM_RBUTTONUP:
1546         case WM_MOUSEWHEEL:
1547             if (window !is null) {
1548                 if (window.onMouse(message, cast(uint)wParam, cast(short)(lParam & 0xFFFF), cast(short)((lParam >> 16) & 0xFFFF)))
1549                     return 0; // processed
1550             }
1551             // not processed - default handling
1552             return DefWindowProc(hwnd, message, wParam, lParam);
1553         case WM_KEYDOWN:
1554         case WM_SYSKEYDOWN:
1555         case WM_KEYUP:
1556         case WM_SYSKEYUP:
1557             if (window !is null) {
1558                 int repeatCount = lParam & 0xFFFF;
1559                 WPARAM vk = wParam;
1560                 WPARAM new_vk = vk;
1561                 UINT scancode = (lParam & 0x00ff0000) >> 16;
1562                 int extended  = (lParam & 0x01000000) != 0;
1563                 switch (vk) {
1564                     case VK_SHIFT:
1565                         new_vk = MapVirtualKey(scancode, 3); //MAPVK_VSC_TO_VK_EX
1566                         break;
1567                     case VK_CONTROL:
1568                         new_vk = extended ? VK_RCONTROL : VK_LCONTROL;
1569                         break;
1570                     case VK_MENU:
1571                         new_vk = extended ? VK_RMENU : VK_LMENU;
1572                         break;
1573                     default:
1574                         // not a key we map from generic to left/right specialized
1575                         //  just return it.
1576                         new_vk = vk;
1577                         break;
1578                 }
1579 
1580                 if (window.onKey(message == WM_KEYDOWN || message == WM_SYSKEYDOWN ? KeyAction.KeyDown : KeyAction.KeyUp, cast(uint)new_vk, repeatCount, 0, message == WM_SYSKEYUP || message == WM_SYSKEYDOWN))
1581                     return 0; // processed
1582             }
1583             break;
1584         case WM_UNICHAR:
1585             if (window !is null) {
1586                 int repeatCount = lParam & 0xFFFF;
1587                 dchar ch = wParam == UNICODE_NOCHAR ? 0 : cast(uint)wParam;
1588                 debug(KeyInput) Log.d("WM_UNICHAR ", ch, " (", cast(int)ch, ")");
1589                 if (window.onKey(KeyAction.Text, cast(uint)wParam, repeatCount, ch))
1590                     return 1; // processed
1591                 return 1;
1592             }
1593             break;
1594         case WM_CHAR:
1595             if (window !is null) {
1596                 int repeatCount = lParam & 0xFFFF;
1597                 dchar ch = wParam == UNICODE_NOCHAR ? 0 : cast(uint)wParam;
1598                 debug(KeyInput) Log.d("WM_CHAR ", ch, " (", cast(int)ch, ")");
1599                 if (window.onKey(KeyAction.Text, cast(uint)wParam, repeatCount, ch))
1600                     return 1; // processed
1601                 return 1;
1602             }
1603             break;
1604         case WM_TIMER:
1605             if (window !is null) {
1606                 window.handleTimer(wParam);
1607                 return 0;
1608             }
1609             break;
1610         case WM_DROPFILES:
1611             if (window !is null) {
1612                 HDROP hdrop = cast(HDROP)wParam;
1613                 string[] files;
1614                 wchar[] buf;
1615                 auto count = DragQueryFileW(hdrop, 0xFFFFFFFF, cast(wchar*)NULL, 0);
1616                 for (int i = 0; i < count; i++) {
1617                     auto sz = DragQueryFileW(hdrop, i, cast(wchar*)NULL, 0);
1618                     buf.length = sz + 2;
1619                     sz = DragQueryFileW(hdrop, i, buf.ptr, sz + 1);
1620                     files ~= toUTF8(buf[0..sz]);
1621                 }
1622                 if (files.length)
1623                     window.handleDroppedFiles(files);
1624                 DragFinish(hdrop);
1625             }
1626             return 0;
1627         case WM_CLOSE:
1628             if (window !is null) {
1629                 bool canClose = window.handleCanClose();
1630                 if (!canClose) {
1631                     Log.d("WM_CLOSE: canClose is false");
1632                     return 0; // prevent closing
1633                 }
1634                 Log.d("WM_CLOSE: closing window ");
1635                 //destroy(window);
1636             }
1637             // default handler inside DefWindowProc will close window
1638             break;
1639         case WM_GETMINMAXINFO:
1640         case WM_NCCREATE:
1641         case WM_NCCALCSIZE:
1642         default:
1643             //Log.d("Unhandled message ", message);
1644             break;
1645     }
1646     if (window)
1647         return window.handleUnknownWindowMessage(message, wParam, lParam);
1648     return DefWindowProc(hwnd, message, wParam, lParam);
1649 }
1650 
1651 //===========================================
1652 // end of version(Windows)
1653 //===========================================
1654