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