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