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