1 // SHE library
2 // Copyright (C) 2012-2018  David Capello
3 //
4 // This file is released under the terms of the MIT license.
5 // Read LICENSE.txt for more information.
6 
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10 
11 #include "she/win/window.h"
12 
13 #include <windowsx.h>
14 #include <commctrl.h>
15 #include <shellapi.h>
16 #include <sstream>
17 
18 #include "base/base.h"
19 #include "base/debug.h"
20 #include "base/log.h"
21 #include "gfx/size.h"
22 #include "she/event.h"
23 #include "she/native_cursor.h"
24 #include "she/win/system.h"
25 #include "she/win/vk.h"
26 #include "she/win/window_dde.h"
27 
28 #define SHE_WND_CLASS_NAME L"Aseprite.Window"
29 
30 #define KEY_TRACE(...)
31 #define MOUSE_TRACE(...)
32 #define TOUCH_TRACE(...)
33 
34 #define kFingerAsMouseTimeout 50
35 
36 // Gets the window client are in absolute/screen coordinates
37 #define ABS_CLIENT_RC(rc)                               \
38   RECT rc;                                              \
39   GetClientRect(m_hwnd, &rc);                           \
40   MapWindowPoints(m_hwnd, NULL, (POINT*)&rc, 2)
41 
42 #ifndef INTERACTION_CONTEXT_PROPERTY_MEASUREMENT_UNITS_SCREEN
43 #define INTERACTION_CONTEXT_PROPERTY_MEASUREMENT_UNITS_SCREEN 1
44 #endif
45 
46 namespace she {
47 
wt_packet_pkcursor_to_pointer_type(int pkCursor)48 static PointerType wt_packet_pkcursor_to_pointer_type(int pkCursor)
49 {
50   switch (pkCursor) {
51     case 0:
52     case 3:
53       return PointerType::Cursor;
54     case 1:
55     case 4:
56       return PointerType::Pen;
57     case 2:
58     case 5:
59     case 6: // Undocumented: Inverted stylus when EnableMouseInPointer() is on
60       return PointerType::Eraser;
61   }
62   return PointerType::Unknown;
63 }
64 
Touch()65 WinWindow::Touch::Touch()
66   : fingers(0)
67   , canBeMouse(false)
68   , asMouse(false)
69   , timerID(0)
70 {
71 }
72 
WinWindow(int width,int height,int scale)73 WinWindow::WinWindow(int width, int height, int scale)
74   : m_hwnd(nullptr)
75   , m_hcursor(nullptr)
76   , m_clientSize(1, 1)
77   , m_restoredSize(0, 0)
78   , m_scale(scale)
79   , m_isCreated(false)
80   , m_translateDeadKeys(false)
81   , m_hasMouse(false)
82   , m_captureMouse(false)
83   , m_customHcursor(false)
84   , m_usePointerApi(false)
85   , m_lastPointerId(0)
86   , m_ictx(nullptr)
87   , m_ignoreRandomMouseEvents(0)
88   // True by default, we prefer to interpret one finger as mouse movement
89   , m_touch(new Touch)
90 #if SHE_USE_POINTER_API_FOR_MOUSE
91   , m_emulateDoubleClick(false)
92   , m_doubleClickMsecs(GetDoubleClickTime())
93   , m_lastPointerDownTime(0)
94   , m_lastPointerDownButton(Event::NoneButton)
95   , m_pointerDownCount(0)
96 #endif
97   , m_hpenctx(nullptr)
98   , m_pointerType(PointerType::Unknown)
99   , m_pressure(0.0)
100 {
101   auto& winApi = system()->winApi();
102   if (winApi.EnableMouseInPointer &&
103       winApi.IsMouseInPointerEnabled &&
104       winApi.GetPointerInfo &&
105       winApi.GetPointerPenInfo) {
106     // Do not enable pointer API for mouse events because:
107     // - Wacom driver doesn't inform their messages in a correct
108     //   pointer API format (events from pen are reported as mouse
109     //   events and without eraser tip information).
110     // - We have to emulate the double-click for the regular mouse
111     //   (search for m_emulateDoubleClick).
112     // - Double click with Wacom stylus doesn't work.
113 #if SHE_USE_POINTER_API_FOR_MOUSE
114     if (!winApi.IsMouseInPointerEnabled()) {
115       // Prefer pointer messages (WM_POINTER*) since Windows 8 instead
116       // of mouse messages (WM_MOUSE*)
117       winApi.EnableMouseInPointer(TRUE);
118       m_emulateDoubleClick =
119         (winApi.IsMouseInPointerEnabled() ? true: false);
120     }
121 #endif
122 
123     // Initialize a Interaction Context to convert WM_POINTER messages
124     // into gestures processed by handleInteractionContextOutput().
125     if (winApi.CreateInteractionContext &&
126         winApi.RegisterOutputCallbackInteractionContext &&
127         winApi.SetInteractionConfigurationInteractionContext) {
128       HRESULT hr = winApi.CreateInteractionContext(&m_ictx);
129       if (SUCCEEDED(hr)) {
130         hr = winApi.RegisterOutputCallbackInteractionContext(
131           m_ictx, &WinWindow::staticInteractionContextCallback, this);
132       }
133       if (SUCCEEDED(hr)) {
134         INTERACTION_CONTEXT_CONFIGURATION cfg[] = {
135           { INTERACTION_ID_MANIPULATION,
136             INTERACTION_CONFIGURATION_FLAG_MANIPULATION |
137             INTERACTION_CONFIGURATION_FLAG_MANIPULATION_TRANSLATION_X |
138             INTERACTION_CONFIGURATION_FLAG_MANIPULATION_TRANSLATION_Y |
139             INTERACTION_CONFIGURATION_FLAG_MANIPULATION_SCALING |
140             INTERACTION_CONFIGURATION_FLAG_MANIPULATION_TRANSLATION_INERTIA |
141             INTERACTION_CONFIGURATION_FLAG_MANIPULATION_SCALING_INERTIA },
142           { INTERACTION_ID_TAP,
143             INTERACTION_CONFIGURATION_FLAG_TAP |
144             INTERACTION_CONFIGURATION_FLAG_TAP_DOUBLE },
145           { INTERACTION_ID_SECONDARY_TAP,
146             INTERACTION_CONFIGURATION_FLAG_SECONDARY_TAP },
147           { INTERACTION_ID_HOLD,
148             INTERACTION_CONFIGURATION_FLAG_NONE },
149           { INTERACTION_ID_DRAG,
150             INTERACTION_CONFIGURATION_FLAG_NONE },
151           { INTERACTION_ID_CROSS_SLIDE,
152             INTERACTION_CONFIGURATION_FLAG_NONE }
153         };
154         hr = winApi.SetInteractionConfigurationInteractionContext(
155           m_ictx, sizeof(cfg) / sizeof(INTERACTION_CONTEXT_CONFIGURATION), cfg);
156       }
157       if (SUCCEEDED(hr)) {
158         hr = winApi.SetPropertyInteractionContext(
159           m_ictx,
160           INTERACTION_CONTEXT_PROPERTY_MEASUREMENT_UNITS,
161           INTERACTION_CONTEXT_PROPERTY_MEASUREMENT_UNITS_SCREEN);
162       }
163     }
164 
165     m_usePointerApi = true;
166   }
167 
168   registerClass();
169 
170   // The HWND returned by CreateWindowEx() is different than the
171   // HWND used in WM_CREATE message.
172   m_hwnd = createHwnd(this, width, height);
173   if (!m_hwnd)
174     throw std::runtime_error("Error creating window");
175 
176   SetWindowLongPtr(m_hwnd, GWLP_USERDATA,
177                    reinterpret_cast<LONG_PTR>(this));
178 
179   // This flag is used to avoid calling T::resizeImpl() when we
180   // add the scrollbars to the window. (As the T type could not be
181   // fully initialized yet.)
182   m_isCreated = true;
183 }
184 
~WinWindow()185 WinWindow::~WinWindow()
186 {
187   auto& winApi = system()->winApi();
188   if (m_ictx && winApi.DestroyInteractionContext)
189     winApi.DestroyInteractionContext(m_ictx);
190 
191   if (m_hwnd)
192     DestroyWindow(m_hwnd);
193 }
194 
queueEvent(Event & ev)195 void WinWindow::queueEvent(Event& ev)
196 {
197   onQueueEvent(ev);
198 }
199 
setScale(int scale)200 void WinWindow::setScale(int scale)
201 {
202   m_scale = scale;
203   onResize(m_clientSize);
204 }
205 
setVisible(bool visible)206 void WinWindow::setVisible(bool visible)
207 {
208   if (visible) {
209     ShowWindow(m_hwnd, SW_SHOWNORMAL);
210     UpdateWindow(m_hwnd);
211     DrawMenuBar(m_hwnd);
212   }
213   else
214     ShowWindow(m_hwnd, SW_HIDE);
215 }
216 
maximize()217 void WinWindow::maximize()
218 {
219   ShowWindow(m_hwnd, SW_MAXIMIZE);
220 }
221 
isMaximized() const222 bool WinWindow::isMaximized() const
223 {
224   return (IsZoomed(m_hwnd) ? true: false);
225 }
226 
isMinimized() const227 bool WinWindow::isMinimized() const
228 {
229   return (GetWindowLong(m_hwnd, GWL_STYLE) & WS_MINIMIZE ? true: false);
230 }
231 
clientSize() const232 gfx::Size WinWindow::clientSize() const
233 {
234   return m_clientSize;
235 }
236 
restoredSize() const237 gfx::Size WinWindow::restoredSize() const
238 {
239   return m_restoredSize;
240 }
241 
setTitle(const std::string & title)242 void WinWindow::setTitle(const std::string& title)
243 {
244   SetWindowText(m_hwnd, base::from_utf8(title).c_str());
245 }
246 
captureMouse()247 void WinWindow::captureMouse()
248 {
249   m_captureMouse = true;
250 
251   if (GetCapture() != m_hwnd) {
252     MOUSE_TRACE("SetCapture\n");
253     SetCapture(m_hwnd);
254   }
255 }
256 
releaseMouse()257 void WinWindow::releaseMouse()
258 {
259   m_captureMouse = false;
260 
261   if (GetCapture() == m_hwnd) {
262     MOUSE_TRACE("ReleaseCapture\n");
263     ReleaseCapture();
264   }
265 }
266 
setMousePosition(const gfx::Point & position)267 void WinWindow::setMousePosition(const gfx::Point& position)
268 {
269   POINT pos = { position.x * m_scale,
270                 position.y * m_scale };
271   ClientToScreen(m_hwnd, &pos);
272   SetCursorPos(pos.x, pos.y);
273 }
274 
setNativeMouseCursor(NativeCursor cursor)275 bool WinWindow::setNativeMouseCursor(NativeCursor cursor)
276 {
277   HCURSOR hcursor = NULL;
278 
279   switch (cursor) {
280     case kNoCursor:
281       // Do nothing, just set to null
282       break;
283     case kArrowCursor:
284       hcursor = LoadCursor(NULL, IDC_ARROW);
285       break;
286     case kCrosshairCursor:
287       hcursor = LoadCursor(NULL, IDC_CROSS);
288       break;
289     case kIBeamCursor:
290       hcursor = LoadCursor(NULL, IDC_IBEAM);
291       break;
292     case kWaitCursor:
293       hcursor = LoadCursor(NULL, IDC_WAIT);
294       break;
295     case kLinkCursor:
296       hcursor = LoadCursor(NULL, IDC_HAND);
297       break;
298     case kHelpCursor:
299       hcursor = LoadCursor(NULL, IDC_HELP);
300       break;
301     case kForbiddenCursor:
302       hcursor = LoadCursor(NULL, IDC_NO);
303       break;
304     case kMoveCursor:
305       hcursor = LoadCursor(NULL, IDC_SIZEALL);
306       break;
307     case kSizeNCursor:
308     case kSizeNSCursor:
309     case kSizeSCursor:
310       hcursor = LoadCursor(NULL, IDC_SIZENS);
311       break;
312     case kSizeECursor:
313     case kSizeWCursor:
314     case kSizeWECursor:
315       hcursor = LoadCursor(NULL, IDC_SIZEWE);
316       break;
317     case kSizeNWCursor:
318     case kSizeSECursor:
319       hcursor = LoadCursor(NULL, IDC_SIZENWSE);
320       break;
321     case kSizeNECursor:
322     case kSizeSWCursor:
323       hcursor = LoadCursor(NULL, IDC_SIZENESW);
324       break;
325   }
326 
327   return setCursor(hcursor, false);
328 }
329 
setNativeMouseCursor(const she::Surface * surface,const gfx::Point & focus,const int scale)330 bool WinWindow::setNativeMouseCursor(const she::Surface* surface,
331                                      const gfx::Point& focus,
332                                      const int scale)
333 {
334   ASSERT(surface);
335 
336   SurfaceFormatData format;
337   surface->getFormat(&format);
338 
339   // Only for 32bpp surfaces
340   if (format.bitsPerPixel != 32)
341     return false;
342 
343   // Based on the following article "How To Create an Alpha
344   // Blended Cursor or Icon in Windows XP":
345   // https://support.microsoft.com/en-us/kb/318876
346 
347   int w = scale*surface->width();
348   int h = scale*surface->height();
349 
350   BITMAPV5HEADER bi;
351   ZeroMemory(&bi, sizeof(BITMAPV5HEADER));
352   bi.bV5Size = sizeof(BITMAPV5HEADER);
353   bi.bV5Width = w;
354   bi.bV5Height = h;
355   bi.bV5Planes = 1;
356   bi.bV5BitCount = 32;
357   bi.bV5Compression = BI_BITFIELDS;
358   bi.bV5RedMask = 0x00ff0000;
359   bi.bV5GreenMask = 0x0000ff00;
360   bi.bV5BlueMask = 0x000000ff;
361   bi.bV5AlphaMask = 0xff000000;
362 
363   uint32_t* bits;
364   HDC hdc = GetDC(nullptr);
365   HBITMAP hbmp = CreateDIBSection(
366     hdc, (BITMAPINFO*)&bi, DIB_RGB_COLORS,
367     (void**)&bits, NULL, (DWORD)0);
368   ReleaseDC(nullptr, hdc);
369   if (!hbmp)
370     return false;
371 
372   for (int y=0; y<h; ++y) {
373     const uint32_t* ptr = (const uint32_t*)surface->getData(0, (h-1-y)/scale);
374     for (int x=0, u=0; x<w; ++x, ++bits) {
375       uint32_t c = *ptr;
376       *bits =
377         (((c & format.alphaMask) >> format.alphaShift) << 24) |
378         (((c & format.redMask  ) >> format.redShift  ) << 16) |
379         (((c & format.greenMask) >> format.greenShift) << 8) |
380         (((c & format.blueMask ) >> format.blueShift ));
381       if (++u == scale) {
382         u = 0;
383         ++ptr;
384       }
385     }
386   }
387 
388   // Create an empty mask bitmap.
389   HBITMAP hmonobmp = CreateBitmap(w, h, 1, 1, nullptr);
390   if (!hmonobmp) {
391     DeleteObject(hbmp);
392     return false;
393   }
394 
395   ICONINFO ii;
396   ii.fIcon = FALSE;
397   ii.xHotspot = scale*focus.x + scale/2;
398   ii.yHotspot = scale*focus.y + scale/2;
399   ii.hbmMask = hmonobmp;
400   ii.hbmColor = hbmp;
401 
402   HCURSOR hcursor = CreateIconIndirect(&ii);
403 
404   DeleteObject(hbmp);
405   DeleteObject(hmonobmp);
406 
407   return setCursor(hcursor, true);
408 }
409 
updateWindow(const gfx::Rect & bounds)410 void WinWindow::updateWindow(const gfx::Rect& bounds)
411 {
412   RECT rc = { bounds.x*m_scale,
413               bounds.y*m_scale,
414               bounds.x*m_scale+bounds.w*m_scale,
415               bounds.y*m_scale+bounds.h*m_scale };
416   InvalidateRect(m_hwnd, &rc, FALSE);
417   UpdateWindow(m_hwnd);
418 }
419 
getLayout()420 std::string WinWindow::getLayout()
421 {
422   WINDOWPLACEMENT wp;
423   wp.length = sizeof(WINDOWPLACEMENT);
424   if (GetWindowPlacement(m_hwnd, &wp)) {
425     std::ostringstream s;
426     s << 1 << ' '
427       << wp.flags << ' '
428       << wp.showCmd << ' '
429       << wp.ptMinPosition.x << ' '
430       << wp.ptMinPosition.y << ' '
431       << wp.ptMaxPosition.x << ' '
432       << wp.ptMaxPosition.y << ' '
433       << wp.rcNormalPosition.left << ' '
434       << wp.rcNormalPosition.top << ' '
435       << wp.rcNormalPosition.right << ' '
436       << wp.rcNormalPosition.bottom;
437     return s.str();
438   }
439   return "";
440 }
441 
setLayout(const std::string & layout)442 void WinWindow::setLayout(const std::string& layout)
443 {
444   WINDOWPLACEMENT wp;
445   wp.length = sizeof(WINDOWPLACEMENT);
446 
447   std::istringstream s(layout);
448   int ver;
449   s >> ver;
450   if (ver == 1) {
451     s >> wp.flags
452       >> wp.showCmd
453       >> wp.ptMinPosition.x
454       >> wp.ptMinPosition.y
455       >> wp.ptMaxPosition.x
456       >> wp.ptMaxPosition.y
457       >> wp.rcNormalPosition.left
458       >> wp.rcNormalPosition.top
459       >> wp.rcNormalPosition.right
460       >> wp.rcNormalPosition.bottom;
461   }
462   else
463     return;
464 
465   if (SetWindowPlacement(m_hwnd, &wp)) {
466     // TODO use the return value
467   }
468 }
469 
setTranslateDeadKeys(bool state)470 void WinWindow::setTranslateDeadKeys(bool state)
471 {
472   m_translateDeadKeys = state;
473 
474   // Here we clear dead keys so we don't get those keys in the new
475   // "translate dead keys" state. E.g. If we focus a text entry
476   // field and the translation of dead keys is enabled, we don't
477   // want to get previous dead keys. The same in case we leave the
478   // text field with a pending dead key, that dead key must be
479   // discarded.
480   VkToUnicode tu;
481   if (tu) {
482     tu.toUnicode(VK_SPACE, 0);
483     if (tu.size() != 0)
484       tu.toUnicode(VK_SPACE, 0);
485   }
486 }
487 
setInterpretOneFingerGestureAsMouseMovement(bool state)488 void WinWindow::setInterpretOneFingerGestureAsMouseMovement(bool state)
489 {
490   if (state) {
491     if (!m_touch)
492       m_touch = new Touch;
493   }
494   else if (m_touch) {
495     killTouchTimer();
496     delete m_touch;
497     m_touch = nullptr;
498   }
499 }
500 
setCursor(HCURSOR hcursor,bool custom)501 bool WinWindow::setCursor(HCURSOR hcursor, bool custom)
502 {
503   SetCursor(hcursor);
504   if (m_hcursor && m_customHcursor)
505     DestroyIcon(m_hcursor);
506   m_hcursor = hcursor;
507   m_customHcursor = custom;
508   return (hcursor ? true: false);
509 }
510 
wndProc(UINT msg,WPARAM wparam,LPARAM lparam)511 LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam)
512 {
513   switch (msg) {
514 
515     case WM_CREATE:
516       LOG("WIN: Creating window %p\n", m_hwnd);
517 
518       if (system()->useWintabAPI()) {
519         // Attach Wacom context
520         m_hpenctx = system()->penApi().open(m_hwnd);
521       }
522       break;
523 
524     case WM_DESTROY:
525       LOG("WIN: Destroying window %p (pen context %p)\n",
526           m_hwnd, m_hpenctx);
527 
528       if (m_hpenctx) {
529         system()->penApi().close(m_hpenctx);
530         m_hpenctx = nullptr;
531       }
532       break;
533 
534     case WM_SETCURSOR:
535       if (LOWORD(lparam) == HTCLIENT) {
536         SetCursor(m_hcursor);
537         return TRUE;
538       }
539       break;
540 
541     case WM_CLOSE: {
542       Event ev;
543       ev.setType(Event::CloseDisplay);
544       queueEvent(ev);
545 
546       // Don't close the window, it must be closed manually after
547       // the CloseDisplay event is processed.
548       return 0;
549     }
550 
551     case WM_PAINT:
552       if (m_isCreated) {
553         PAINTSTRUCT ps;
554         HDC hdc = BeginPaint(m_hwnd, &ps);
555         onPaint(hdc);
556         EndPaint(m_hwnd, &ps);
557         return true;
558       }
559       break;
560 
561     case WM_SIZE:
562       if (m_isCreated) {
563         int w = GET_X_LPARAM(lparam);
564         int h = GET_Y_LPARAM(lparam);
565 
566         if (w > 0 && h > 0) {
567           m_clientSize.w = w;
568           m_clientSize.h = h;
569           onResize(m_clientSize);
570         }
571 
572         WINDOWPLACEMENT pl;
573         pl.length = sizeof(pl);
574         if (GetWindowPlacement(m_hwnd, &pl)) {
575           m_restoredSize = gfx::Size(
576             pl.rcNormalPosition.right - pl.rcNormalPosition.left,
577             pl.rcNormalPosition.bottom - pl.rcNormalPosition.top);
578         }
579       }
580       break;
581 
582     // Mouse and Trackpad Messages
583 
584     case WM_MOUSEMOVE: {
585       Event ev;
586       mouseEvent(lparam, ev);
587 
588       MOUSE_TRACE("MOUSEMOVE xy=%d,%d\n",
589                   ev.position().x, ev.position().y);
590 
591       if (m_ignoreRandomMouseEvents > 0) {
592         MOUSE_TRACE(" - IGNORED\n");
593         --m_ignoreRandomMouseEvents;
594         break;
595       }
596 
597       if (!m_hasMouse) {
598         m_hasMouse = true;
599 
600         ev.setType(Event::MouseEnter);
601         queueEvent(ev);
602 
603         MOUSE_TRACE("-> Event::MouseEnter\n");
604 
605         // Track mouse to receive WM_MOUSELEAVE message.
606         TRACKMOUSEEVENT tme;
607         tme.cbSize = sizeof(TRACKMOUSEEVENT);
608         tme.dwFlags = TME_LEAVE;
609         tme.hwndTrack = m_hwnd;
610         _TrackMouseEvent(&tme);
611       }
612 
613       if (m_pointerType != PointerType::Unknown) {
614         ev.setPointerType(m_pointerType);
615         ev.setPressure(m_pressure);
616       }
617 
618       ev.setType(Event::MouseMove);
619       queueEvent(ev);
620       break;
621     }
622 
623     case WM_NCMOUSEMOVE:
624     case WM_MOUSELEAVE:
625       if (m_hasMouse) {
626         m_hasMouse = false;
627 
628         Event ev;
629         ev.setType(Event::MouseLeave);
630         ev.setModifiers(get_modifiers_from_last_win32_message());
631         queueEvent(ev);
632 
633         MOUSE_TRACE("-> Event::MouseLeave\n");
634       }
635       break;
636 
637     case WM_LBUTTONDOWN:
638     case WM_RBUTTONDOWN:
639     case WM_MBUTTONDOWN:
640     case WM_XBUTTONDOWN: {
641       Event ev;
642       mouseEvent(lparam, ev);
643       ev.setType(Event::MouseDown);
644       ev.setButton(
645         msg == WM_LBUTTONDOWN ? Event::LeftButton:
646         msg == WM_RBUTTONDOWN ? Event::RightButton:
647         msg == WM_MBUTTONDOWN ? Event::MiddleButton:
648         msg == WM_XBUTTONDOWN && GET_XBUTTON_WPARAM(wparam) == 1 ? Event::X1Button:
649         msg == WM_XBUTTONDOWN && GET_XBUTTON_WPARAM(wparam) == 2 ? Event::X2Button:
650         Event::NoneButton);
651 
652       if (m_pointerType != PointerType::Unknown) {
653         ev.setPointerType(m_pointerType);
654         ev.setPressure(m_pressure);
655       }
656       queueEvent(ev);
657 
658       MOUSE_TRACE("BUTTONDOWN xy=%d,%d button=%d\n",
659                   ev.position().x, ev.position().y,
660                   ev.button());
661       break;
662     }
663 
664     case WM_LBUTTONUP:
665     case WM_RBUTTONUP:
666     case WM_MBUTTONUP:
667     case WM_XBUTTONUP: {
668       Event ev;
669       mouseEvent(lparam, ev);
670       ev.setType(Event::MouseUp);
671       ev.setButton(
672         msg == WM_LBUTTONUP ? Event::LeftButton:
673         msg == WM_RBUTTONUP ? Event::RightButton:
674         msg == WM_MBUTTONUP ? Event::MiddleButton:
675         msg == WM_XBUTTONUP && GET_XBUTTON_WPARAM(wparam) == 1 ? Event::X1Button:
676         msg == WM_XBUTTONUP && GET_XBUTTON_WPARAM(wparam) == 2 ? Event::X2Button:
677         Event::NoneButton);
678 
679       if (m_pointerType != PointerType::Unknown) {
680         ev.setPointerType(m_pointerType);
681         ev.setPressure(m_pressure);
682       }
683       queueEvent(ev);
684 
685       MOUSE_TRACE("BUTTONUP xy=%d,%d button=%d\n",
686                   ev.position().x, ev.position().y,
687                   ev.button());
688 
689       // Avoid popup menu for scrollbars
690       if (msg == WM_RBUTTONUP)
691         return 0;
692 
693       break;
694     }
695 
696     case WM_LBUTTONDBLCLK:
697     case WM_MBUTTONDBLCLK:
698     case WM_RBUTTONDBLCLK:
699     case WM_XBUTTONDBLCLK: {
700       Event ev;
701       mouseEvent(lparam, ev);
702       ev.setType(Event::MouseDoubleClick);
703       ev.setButton(
704         msg == WM_LBUTTONDBLCLK ? Event::LeftButton:
705         msg == WM_RBUTTONDBLCLK ? Event::RightButton:
706         msg == WM_MBUTTONDBLCLK ? Event::MiddleButton:
707         msg == WM_XBUTTONDBLCLK && GET_XBUTTON_WPARAM(wparam) == 1 ? Event::X1Button:
708         msg == WM_XBUTTONDBLCLK && GET_XBUTTON_WPARAM(wparam) == 2 ? Event::X2Button:
709         Event::NoneButton);
710 
711       if (m_pointerType != PointerType::Unknown) {
712         ev.setPointerType(m_pointerType);
713         ev.setPressure(m_pressure);
714       }
715       queueEvent(ev);
716 
717       MOUSE_TRACE("BUTTONDBLCLK xy=%d,%d button=%d\n",
718                   ev.position().x, ev.position().y,
719                   ev.button());
720       break;
721     }
722 
723     case WM_MOUSEWHEEL:
724     case WM_MOUSEHWHEEL: {
725       POINT pos = { GET_X_LPARAM(lparam),
726                     GET_Y_LPARAM(lparam) };
727       ScreenToClient(m_hwnd, &pos);
728 
729       Event ev;
730       ev.setType(Event::MouseWheel);
731       ev.setModifiers(get_modifiers_from_last_win32_message());
732       ev.setPosition(gfx::Point(pos.x, pos.y) / m_scale);
733 
734       int z = GET_WHEEL_DELTA_WPARAM(wparam);
735       if (ABS(z) >= WHEEL_DELTA)
736         z /= WHEEL_DELTA;
737       else {
738         // TODO use floating point numbers or something similar
739         //      (so we could use: z /= double(WHEEL_DELTA))
740         z = SGN(z);
741       }
742 
743       gfx::Point delta(
744         (msg == WM_MOUSEHWHEEL ? z: 0),
745         (msg == WM_MOUSEWHEEL ? -z: 0));
746       ev.setWheelDelta(delta);
747       queueEvent(ev);
748 
749       MOUSE_TRACE("MOUSEWHEEL xy=%d,%d delta=%d,%d\n",
750                   ev.position().x, ev.position().y,
751                   ev.wheelDelta().x, ev.wheelDelta().y);
752       break;
753     }
754 
755     case WM_HSCROLL:
756     case WM_VSCROLL: {
757       POINT pos;
758       GetCursorPos(&pos);
759       ScreenToClient(m_hwnd, &pos);
760 
761       Event ev;
762       ev.setType(Event::MouseWheel);
763       ev.setModifiers(get_modifiers_from_last_win32_message());
764       ev.setPosition(gfx::Point(pos.x, pos.y) / m_scale);
765 
766       int bar = (msg == WM_HSCROLL ? SB_HORZ: SB_VERT);
767       int z = GetScrollPos(m_hwnd, bar);
768 
769       switch (LOWORD(wparam)) {
770         case SB_LEFT:
771         case SB_LINELEFT:
772           --z;
773           break;
774         case SB_PAGELEFT:
775           z -= 2;
776           break;
777         case SB_RIGHT:
778         case SB_LINERIGHT:
779           ++z;
780           break;
781         case SB_PAGERIGHT:
782           z += 2;
783           break;
784         case SB_THUMBPOSITION:
785         case SB_THUMBTRACK:
786         case SB_ENDSCROLL:
787           // Do nothing
788           break;
789       }
790 
791       gfx::Point delta(
792         (msg == WM_HSCROLL ? (z-50): 0),
793         (msg == WM_VSCROLL ? (z-50): 0));
794       ev.setWheelDelta(delta);
795 
796       SetScrollPos(m_hwnd, bar, 50, FALSE);
797       queueEvent(ev);
798 
799       MOUSE_TRACE("HVSCROLL xy=%d,%d delta=%d,%d\n",
800                   ev.position().x, ev.position().y,
801                   ev.wheelDelta().x, ev.wheelDelta().y);
802       break;
803     }
804 
805     // Pointer API (since Windows 8.0)
806 
807     case WM_POINTERCAPTURECHANGED: {
808       MOUSE_TRACE("POINTERCAPTURECHANGED\n");
809       releaseMouse();
810       break;
811     }
812 
813     case WM_POINTERENTER: {
814       POINTER_INFO pi;
815       Event ev;
816       if (!pointerEvent(wparam, ev, pi))
817         break;
818 
819       MOUSE_TRACE("POINTERENTER id=%d xy=%d,%d\n",
820                   pi.pointerId, ev.position().x, ev.position().y);
821 
822       if (pi.pointerType == PT_TOUCH || pi.pointerType == PT_PEN) {
823         auto& winApi = system()->winApi();
824         if (m_ictx && winApi.AddPointerInteractionContext) {
825           winApi.AddPointerInteractionContext(m_ictx, pi.pointerId);
826 
827           if (m_touch && pi.pointerType == PT_TOUCH &&
828               !m_touch->asMouse) {
829             ++m_touch->fingers;
830             TOUCH_TRACE("POINTERENTER fingers=%d\n", m_touch->fingers);
831             if (m_touch->fingers == 1) {
832               waitTimerToConvertFingerAsMouseMovement();
833             }
834             else if (m_touch->canBeMouse && m_touch->fingers >= 2) {
835               delegateFingerToInteractionContext();
836             }
837           }
838         }
839       }
840 
841       if (!m_hasMouse) {
842         m_hasMouse = true;
843 
844         ev.setType(Event::MouseEnter);
845         queueEvent(ev);
846 
847         MOUSE_TRACE("-> Event::MouseEnter\n");
848       }
849       return 0;
850     }
851 
852     case WM_POINTERLEAVE: {
853       POINTER_INFO pi;
854       Event ev;
855       if (!pointerEvent(wparam, ev, pi))
856         break;
857 
858       MOUSE_TRACE("POINTERLEAVE id=%d\n", pi.pointerId);
859 
860       // After releasing a finger a WM_MOUSEMOVE event in the trackpad
861       // position is generated, we'll ignore that message.
862       if (m_touch)
863         m_ignoreRandomMouseEvents = 1;
864       else
865         m_ignoreRandomMouseEvents = 0;
866 
867       if (pi.pointerType == PT_TOUCH || pi.pointerType == PT_PEN) {
868         auto& winApi = system()->winApi();
869         if (m_ictx && winApi.RemovePointerInteractionContext) {
870           winApi.RemovePointerInteractionContext(m_ictx, pi.pointerId);
871 
872           if (m_touch && pi.pointerType == PT_TOUCH) {
873             if (m_touch->fingers > 0)
874               --m_touch->fingers;
875             TOUCH_TRACE("POINTERLEAVE fingers=%d\n", m_touch->fingers);
876             if (m_touch->fingers == 0) {
877               if (m_touch->canBeMouse)
878                 sendDelayedTouchEvents();
879               else
880                 clearDelayedTouchEvents();
881               killTouchTimer();
882               m_touch->asMouse = false;
883             }
884           }
885         }
886       }
887 
888 #if 0 // Don't generate MouseLeave from pen/touch messages
889       // TODO we should generate this message, but after this touch
890       //      messages don't work anymore, so we have to fix that problem.
891       if (m_hasMouse) {
892         m_hasMouse = false;
893 
894         ev.setType(Event::MouseLeave);
895         queueEvent(ev);
896 
897         MOUSE_TRACE("-> Event::MouseLeave\n");
898         return 0;
899       }
900 #endif
901       break;
902     }
903 
904     case WM_POINTERDOWN: {
905       POINTER_INFO pi;
906       Event ev;
907       if (!pointerEvent(wparam, ev, pi))
908         break;
909 
910       if (pi.pointerType == PT_TOUCH || pi.pointerType == PT_PEN) {
911         auto& winApi = system()->winApi();
912         if (m_ictx && winApi.ProcessPointerFramesInteractionContext) {
913           winApi.ProcessPointerFramesInteractionContext(m_ictx, 1, 1, &pi);
914           if (!m_touch && pi.pointerType == PT_TOUCH)
915             return 0;
916         }
917       }
918 
919       handlePointerButtonChange(ev, pi);
920 
921       MOUSE_TRACE("POINTERDOWN id=%d xy=%d,%d button=%d\n",
922                   pi.pointerId, ev.position().x, ev.position().y,
923                   ev.button());
924       return 0;
925     }
926 
927     case WM_POINTERUP: {
928       POINTER_INFO pi;
929       Event ev;
930       if (!pointerEvent(wparam, ev, pi))
931         break;
932 
933       if (pi.pointerType == PT_TOUCH || pi.pointerType == PT_PEN) {
934         auto& winApi = system()->winApi();
935         if (m_ictx && winApi.ProcessPointerFramesInteractionContext) {
936           winApi.ProcessPointerFramesInteractionContext(m_ictx, 1, 1, &pi);
937           if (!m_touch && pi.pointerType == PT_TOUCH)
938             return 0;
939         }
940       }
941 
942       handlePointerButtonChange(ev, pi);
943 
944       MOUSE_TRACE("POINTERUP id=%d xy=%d,%d button=%d\n",
945                   pi.pointerId, ev.position().x, ev.position().y,
946                   ev.button());
947       return 0;
948     }
949 
950     case WM_POINTERUPDATE: {
951       POINTER_INFO pi;
952       Event ev;
953       if (!pointerEvent(wparam, ev, pi))
954         break;
955 
956       // See the comment for m_ignoreRandomMouseEvents variable, and
957       // why here is = 2.
958       m_ignoreRandomMouseEvents = 2;
959 
960       if (pi.pointerType == PT_TOUCH || pi.pointerType == PT_PEN) {
961         auto& winApi = system()->winApi();
962         if (m_ictx && winApi.ProcessPointerFramesInteractionContext) {
963           winApi.ProcessPointerFramesInteractionContext(m_ictx, 1, 1, &pi);
964           if (!m_touch && pi.pointerType == PT_TOUCH)
965             return 0;
966         }
967       }
968 
969       if (!m_hasMouse) {
970         m_hasMouse = true;
971 
972         ev.setType(Event::MouseEnter);
973         queueEvent(ev);
974 
975         MOUSE_TRACE("-> Event::MouseEnter\n");
976       }
977 
978       ev.setType(Event::MouseMove);
979 
980       if (m_touch && pi.pointerType == PT_TOUCH) {
981         TOUCH_TRACE("POINTERUPDATE canBeMouse=%d asMouse=%d\n",
982                     m_touch->canBeMouse,
983                     m_touch->asMouse);
984         if (!m_touch->asMouse) {
985           if (m_touch->canBeMouse)
986             m_touch->delayedEvents.push_back(ev);
987           else
988             return 0;
989         }
990         else
991           queueEvent(ev);
992       }
993       else
994         queueEvent(ev);
995 
996       handlePointerButtonChange(ev, pi);
997 
998       MOUSE_TRACE("POINTERUPDATE id=%d xy=%d,%d\n",
999                   pi.pointerId, ev.position().x, ev.position().y);
1000       return 0;
1001     }
1002 
1003     case WM_POINTERWHEEL:
1004     case WM_POINTERHWHEEL: {
1005       POINTER_INFO pi;
1006       Event ev;
1007       if (!pointerEvent(wparam, ev, pi))
1008         break;
1009 
1010       ev.setType(Event::MouseWheel);
1011 
1012       int z = GET_WHEEL_DELTA_WPARAM(wparam);
1013       if (ABS(z) >= WHEEL_DELTA)
1014         z /= WHEEL_DELTA;
1015       else {
1016         // TODO use floating point numbers or something similar
1017         //      (so we could use: z /= double(WHEEL_DELTA))
1018         z = SGN(z);
1019       }
1020 
1021       gfx::Point delta(
1022         (msg == WM_POINTERHWHEEL ? z: 0),
1023         (msg == WM_POINTERWHEEL ? -z: 0));
1024       ev.setWheelDelta(delta);
1025       queueEvent(ev);
1026 
1027       MOUSE_TRACE("POINTERWHEEL xy=%d,%d delta=%d,%d\n",
1028                   ev.position().x, ev.position().y,
1029                   ev.wheelDelta().x, ev.wheelDelta().y);
1030 
1031       return 0;
1032     }
1033 
1034     case WM_TIMER:
1035       TOUCH_TRACE("TIMER %d\n", wparam);
1036       if (m_touch && m_touch->timerID == wparam) {
1037         killTouchTimer();
1038 
1039         if (!m_touch->asMouse &&
1040             m_touch->canBeMouse &&
1041             m_touch->fingers == 1) {
1042           TOUCH_TRACE("-> finger as mouse, sent %d events\n",
1043                       m_touch->delayedEvents.size());
1044 
1045           convertFingerAsMouseMovement();
1046         }
1047         else {
1048           delegateFingerToInteractionContext();
1049         }
1050       }
1051       break;
1052 
1053     // Keyboard Messages
1054 
1055     case WM_SYSKEYDOWN:
1056     case WM_KEYDOWN: {
1057       int vk = wparam;
1058       int scancode = (lparam >> 16) & 0xff;
1059       bool sendMsg = true;
1060       const KeyScancode sheScancode = win32vk_to_scancode(vk);
1061 
1062       // We only create one KeyDown event for modifiers. Bit 30
1063       // indicates the previous state of the key, if the modifier was
1064       // already pressed don't generate the event.
1065       if ((sheScancode >= kKeyFirstModifierScancode) &&
1066           (lparam & (1 << 30)))
1067         return 0;
1068 
1069       Event ev;
1070       ev.setType(Event::KeyDown);
1071       ev.setModifiers(get_modifiers_from_last_win32_message());
1072       ev.setScancode(sheScancode);
1073       ev.setUnicodeChar(0);
1074       ev.setRepeat(MAX(0, (lparam & 0xffff)-1));
1075 
1076       KEY_TRACE("KEYDOWN vk=%d scancode=%d->%d modifiers=%d\n",
1077                 vk, scancode, ev.scancode(), ev.modifiers());
1078 
1079       {
1080         VkToUnicode tu;
1081         if (tu) {
1082           tu.toUnicode(vk, scancode);
1083           if (tu.isDeadKey()) {
1084             ev.setDeadKey(true);
1085             ev.setUnicodeChar(tu[0]);
1086             if (!m_translateDeadKeys)
1087               tu.toUnicode(vk, scancode); // Call again to remove dead-key
1088           }
1089           else if (tu.size() > 0) {
1090             sendMsg = false;
1091             for (int chr : tu) {
1092               ev.setUnicodeChar(chr);
1093               queueEvent(ev);
1094 
1095               KEY_TRACE(" -> queued unicode char=%d <%c>\n",
1096                         ev.unicodeChar(),
1097                         ev.unicodeChar() ? ev.unicodeChar(): ' ');
1098             }
1099           }
1100         }
1101       }
1102 
1103       if (sendMsg) {
1104         queueEvent(ev);
1105         KEY_TRACE(" -> queued unicode char=%d <%c>\n",
1106                   ev.unicodeChar(),
1107                   ev.unicodeChar() ? ev.unicodeChar(): ' ');
1108       }
1109 
1110       return 0;
1111     }
1112 
1113     case WM_SYSKEYUP:
1114     case WM_KEYUP: {
1115       Event ev;
1116       ev.setType(Event::KeyUp);
1117       ev.setModifiers(get_modifiers_from_last_win32_message());
1118       ev.setScancode(win32vk_to_scancode(wparam));
1119       ev.setUnicodeChar(0);
1120       ev.setRepeat(MAX(0, (lparam & 0xffff)-1));
1121       queueEvent(ev);
1122 
1123       // TODO If we use native menus, this message should be given
1124       // to the DefWindowProc() in some cases (e.g. F10 or Alt keys)
1125       return 0;
1126     }
1127 
1128     case WM_MENUCHAR:
1129       // Avoid playing a sound when Alt+key is pressed and it's not in a native menu
1130       return MAKELONG(0, MNC_CLOSE);
1131 
1132     case WM_DROPFILES: {
1133       HDROP hdrop = (HDROP)(wparam);
1134       base::paths files;
1135 
1136       int count = DragQueryFile(hdrop, 0xFFFFFFFF, NULL, 0);
1137       for (int index=0; index<count; ++index) {
1138         int length = DragQueryFile(hdrop, index, NULL, 0);
1139         if (length > 0) {
1140           std::vector<TCHAR> str(length+1);
1141           DragQueryFile(hdrop, index, &str[0], str.size());
1142           files.push_back(base::to_utf8(&str[0]));
1143         }
1144       }
1145 
1146       DragFinish(hdrop);
1147 
1148       Event ev;
1149       ev.setType(Event::DropFiles);
1150       ev.setFiles(files);
1151       queueEvent(ev);
1152       break;
1153     }
1154 
1155     case WM_NCCALCSIZE: {
1156       if (wparam) {
1157         // Scrollbars must be enabled and visible to get trackpad
1158         // events of old drivers. So we cannot use ShowScrollBar() to
1159         // hide them. This is a simple (maybe not so elegant)
1160         // solution: Expand the client area to we overlap the
1161         // scrollbars. In this way they are not visible, but we still
1162         // get their messages.
1163         NCCALCSIZE_PARAMS* cs = reinterpret_cast<NCCALCSIZE_PARAMS*>(lparam);
1164         cs->rgrc[0].right += GetSystemMetrics(SM_CYVSCROLL);
1165         cs->rgrc[0].bottom += GetSystemMetrics(SM_CYHSCROLL);
1166       }
1167       break;
1168     }
1169 
1170     case WM_NCHITTEST: {
1171       LRESULT result = CallWindowProc(DefWindowProc, m_hwnd, msg, wparam, lparam);
1172       gfx::Point pt(GET_X_LPARAM(lparam),
1173                     GET_Y_LPARAM(lparam));
1174 
1175       ABS_CLIENT_RC(rc);
1176       gfx::Rect area(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top);
1177 
1178       //LOG("NCHITTEST: %d %d - %d %d %d %d - %s\n", pt.x, pt.y, area.x, area.y, area.w, area.h, area.contains(pt) ? "true": "false");
1179 
1180       // We ignore scrollbars so if the mouse is above them, we return
1181       // as it's in the window client or resize area. (Remember that
1182       // we have scroll bars are enabled and visible to receive
1183       // trackpad messages only.)
1184       if (result == HTHSCROLL) {
1185         result = (area.contains(pt) ? HTCLIENT: HTBOTTOM);
1186       }
1187       else if (result == HTVSCROLL) {
1188         result = (area.contains(pt) ? HTCLIENT: HTRIGHT);
1189       }
1190 
1191       return result;
1192     }
1193 
1194     // Wintab API Messages
1195 
1196     case WT_PROXIMITY: {
1197       MOUSE_TRACE("WT_PROXIMITY\n");
1198 
1199       bool entering_ctx = (LOWORD(lparam) ? true: false);
1200       if (!entering_ctx)
1201         m_pointerType = PointerType::Unknown;
1202       break;
1203     }
1204 
1205     case WT_CSRCHANGE: {    // From Wintab 1.1
1206       auto& api = system()->penApi();
1207       UINT serial = wparam;
1208       HCTX ctx = (HCTX)lparam;
1209       PACKET packet;
1210 
1211       if (api.packet(ctx, serial, &packet))
1212         m_pointerType = wt_packet_pkcursor_to_pointer_type(packet.pkCursor);
1213       else
1214         m_pointerType = PointerType::Unknown;
1215 
1216       MOUSE_TRACE("WT_CSRCHANGE pointer=%d\n", m_pointerType);
1217       break;
1218     }
1219 
1220     case WT_PACKET: {
1221       auto& api = system()->penApi();
1222       UINT serial = wparam;
1223       HCTX ctx = (HCTX)lparam;
1224       PACKET packet;
1225 
1226       if (api.packet(ctx, serial, &packet)) {
1227         m_pressure = packet.pkNormalPressure / 1000.0; // TODO get the maximum value
1228         m_pointerType = wt_packet_pkcursor_to_pointer_type(packet.pkCursor);
1229       }
1230       else
1231         m_pointerType = PointerType::Unknown;
1232 
1233       MOUSE_TRACE("WT_PACKET pointer=%d m_pressure=%.16g\n",
1234                   m_pointerType, m_pressure);
1235       break;
1236     }
1237 
1238   }
1239 
1240   LRESULT result = FALSE;
1241   if (handle_dde_messages(m_hwnd, msg, wparam, lparam, result))
1242     return result;
1243 
1244   return DefWindowProc(m_hwnd, msg, wparam, lparam);
1245 }
1246 
mouseEvent(LPARAM lparam,Event & ev)1247 void WinWindow::mouseEvent(LPARAM lparam, Event& ev)
1248 {
1249   ev.setModifiers(get_modifiers_from_last_win32_message());
1250   ev.setPosition(gfx::Point(
1251                    GET_X_LPARAM(lparam) / m_scale,
1252                    GET_Y_LPARAM(lparam) / m_scale));
1253 }
1254 
pointerEvent(WPARAM wparam,Event & ev,POINTER_INFO & pi)1255 bool WinWindow::pointerEvent(WPARAM wparam, Event& ev, POINTER_INFO& pi)
1256 {
1257   if (!m_usePointerApi)
1258     return false;
1259 
1260   auto& winApi = system()->winApi();
1261   if (!winApi.GetPointerInfo(GET_POINTERID_WPARAM(wparam), &pi))
1262     return false;
1263 
1264   ABS_CLIENT_RC(rc);
1265 
1266   ev.setModifiers(get_modifiers_from_last_win32_message());
1267   ev.setPosition(gfx::Point((pi.ptPixelLocation.x - rc.left) / m_scale,
1268                             (pi.ptPixelLocation.y - rc.top) / m_scale));
1269 
1270   switch (pi.pointerType) {
1271     case PT_MOUSE: {
1272       MOUSE_TRACE("pi.pointerType PT_MOUSE\n");
1273       ev.setPointerType(PointerType::Mouse);
1274 
1275       // If we use EnableMouseInPointer(true), events from Wacom
1276       // stylus came as PT_MOUSE instead of PT_PEN with eraser
1277       // flag. This is just insane, EnableMouseInPointer(true) is not
1278       // an option at the moment if we want proper support for Wacom
1279       // events.
1280       break;
1281     }
1282     case PT_TOUCH: {
1283       MOUSE_TRACE("pi.pointerType PT_TOUCH\n");
1284       ev.setPointerType(PointerType::Touch);
1285       break;
1286     }
1287     case PT_TOUCHPAD: {
1288       MOUSE_TRACE("pi.pointerType PT_TOUCHPAD\n");
1289       ev.setPointerType(PointerType::Touchpad);
1290       break;
1291     }
1292     case PT_PEN: {
1293       MOUSE_TRACE("pi.pointerType PT_PEN\n");
1294       ev.setPointerType(PointerType::Pen);
1295 
1296       POINTER_PEN_INFO ppi;
1297       if (winApi.GetPointerPenInfo(pi.pointerId, &ppi)) {
1298         MOUSE_TRACE(" - ppi.penFlags = %d\n", ppi.penFlags);
1299         if (ppi.penFlags & PEN_FLAG_ERASER)
1300           ev.setPointerType(PointerType::Eraser);
1301       }
1302       break;
1303     }
1304   }
1305 
1306   m_lastPointerId = pi.pointerId;
1307   return true;
1308 }
1309 
handlePointerButtonChange(Event & ev,POINTER_INFO & pi)1310 void WinWindow::handlePointerButtonChange(Event& ev, POINTER_INFO& pi)
1311 {
1312   if (pi.ButtonChangeType == POINTER_CHANGE_NONE) {
1313 #if SHE_USE_POINTER_API_FOR_MOUSE
1314     // Reset the counter of pointer down for the emulated double-click
1315     if (m_emulateDoubleClick)
1316       m_pointerDownCount = 0;
1317 #endif
1318     return;
1319   }
1320 
1321   Event::MouseButton button = Event::NoneButton;
1322   bool down = false;
1323 
1324   switch (pi.ButtonChangeType) {
1325     case POINTER_CHANGE_FIRSTBUTTON_DOWN:
1326       down = true;
1327     case POINTER_CHANGE_FIRSTBUTTON_UP:
1328       button = Event::LeftButton;
1329       break;
1330     case  POINTER_CHANGE_SECONDBUTTON_DOWN:
1331       down = true;
1332     case POINTER_CHANGE_SECONDBUTTON_UP:
1333       button = Event::RightButton;
1334       break;
1335     case POINTER_CHANGE_THIRDBUTTON_DOWN:
1336       down = true;
1337     case POINTER_CHANGE_THIRDBUTTON_UP:
1338       button = Event::MiddleButton;
1339       break;
1340     case POINTER_CHANGE_FOURTHBUTTON_DOWN:
1341       down = true;
1342     case POINTER_CHANGE_FOURTHBUTTON_UP:
1343       button = Event::X1Button;
1344       break;
1345     case POINTER_CHANGE_FIFTHBUTTON_DOWN:
1346       down = true;
1347     case POINTER_CHANGE_FIFTHBUTTON_UP:
1348       button = Event::X2Button;
1349       break;
1350   }
1351 
1352   if (button == Event::NoneButton)
1353     return;
1354 
1355   ev.setType(down ? Event::MouseDown: Event::MouseUp);
1356   ev.setButton(button);
1357 
1358 #if SHE_USE_POINTER_API_FOR_MOUSE
1359   if (down && m_emulateDoubleClick) {
1360     if (button != m_lastPointerDownButton)
1361       m_pointerDownCount = 0;
1362 
1363     ++m_pointerDownCount;
1364 
1365     base::tick_t curTime = base::current_tick();
1366     if ((m_pointerDownCount == 2) &&
1367         (curTime - m_lastPointerDownTime) <= m_doubleClickMsecs) {
1368       ev.setType(Event::MouseDoubleClick);
1369       m_pointerDownCount = 0;
1370     }
1371 
1372     m_lastPointerDownTime = curTime;
1373     m_lastPointerDownButton = button;
1374   }
1375 #endif
1376 
1377   if (m_touch && pi.pointerType == PT_TOUCH) {
1378     if (!m_touch->asMouse) {
1379       if (m_touch->canBeMouse) {
1380         // TODO Review why the ui layer needs a Event::MouseMove event
1381         //      before ButtonDown/Up events.
1382         Event evMouseMove = ev;
1383         evMouseMove.setType(Event::MouseMove);
1384         m_touch->delayedEvents.push_back(evMouseMove);
1385         m_touch->delayedEvents.push_back(ev);
1386       }
1387       return;
1388     }
1389   }
1390 
1391   queueEvent(ev);
1392 }
1393 
handleInteractionContextOutput(const INTERACTION_CONTEXT_OUTPUT * output)1394 void WinWindow::handleInteractionContextOutput(
1395   const INTERACTION_CONTEXT_OUTPUT* output)
1396 {
1397   MOUSE_TRACE("%s (%d) xy=%.16g %.16g flags=%d type=%d\n",
1398               output->interactionId == INTERACTION_ID_MANIPULATION ? "INTERACTION_ID_MANIPULATION":
1399               output->interactionId == INTERACTION_ID_TAP ? "INTERACTION_ID_TAP":
1400               output->interactionId == INTERACTION_ID_SECONDARY_TAP ? "INTERACTION_ID_SECONDARY_TAP":
1401               output->interactionId == INTERACTION_ID_HOLD ? "INTERACTION_ID_HOLD": "INTERACTION_ID_???",
1402               output->interactionId,
1403               output->x, output->y,
1404               output->interactionFlags,
1405               output->inputType);
1406 
1407   // We use the InteractionContext to interpret touch gestures only
1408   // and double tap with pen.
1409   if ((output->inputType == PT_TOUCH
1410        && (!m_touch
1411            || (!m_touch->asMouse && !m_touch->canBeMouse)
1412            || (output->arguments.tap.count == 2)))
1413       || (output->inputType == PT_PEN &&
1414           output->interactionId == INTERACTION_ID_TAP &&
1415           output->arguments.tap.count == 2)) {
1416     ABS_CLIENT_RC(rc);
1417 
1418     gfx::Point pos(int((output->x - rc.left) / m_scale),
1419                    int((output->y - rc.top) / m_scale));
1420 
1421     Event ev;
1422     ev.setModifiers(get_modifiers_from_last_win32_message());
1423     ev.setPosition(pos);
1424 
1425     bool hadMouse = m_hasMouse;
1426     if (!m_hasMouse) {
1427       m_hasMouse = true;
1428       ev.setType(Event::MouseEnter);
1429       queueEvent(ev);
1430     }
1431 
1432     switch (output->interactionId) {
1433       case INTERACTION_ID_MANIPULATION: {
1434         MOUSE_TRACE(" - delta xy=%.16g %.16g scale=%.16g expansion=%.16g rotation=%.16g\n",
1435                     output->arguments.manipulation.delta.translationX,
1436                     output->arguments.manipulation.delta.translationY,
1437                     output->arguments.manipulation.delta.scale,
1438                     output->arguments.manipulation.delta.expansion,
1439                     output->arguments.manipulation.delta.rotation);
1440 
1441         gfx::Point delta(-int(output->arguments.manipulation.delta.translationX) / m_scale,
1442                          -int(output->arguments.manipulation.delta.translationY) / m_scale);
1443 
1444         if (output->interactionFlags & INTERACTION_FLAG_BEGIN) {
1445           ev.setType(Event::MouseMove);
1446           queueEvent(ev);
1447         }
1448 
1449         ev.setType(Event::MouseWheel);
1450         ev.setWheelDelta(delta);
1451         ev.setPreciseWheel(true);
1452         queueEvent(ev);
1453 
1454         ev.setType(Event::TouchMagnify);
1455         ev.setMagnification(output->arguments.manipulation.delta.scale - 1.0);
1456         queueEvent(ev);
1457         break;
1458       }
1459 
1460       case INTERACTION_ID_TAP:
1461         MOUSE_TRACE(" - count=%d\n", output->arguments.tap.count);
1462 
1463         ev.setButton(Event::LeftButton);
1464         ev.setType(Event::MouseMove); queueEvent(ev);
1465         if (output->arguments.tap.count == 2) {
1466           ev.setType(Event::MouseDoubleClick); queueEvent(ev);
1467         }
1468         else {
1469           ev.setType(Event::MouseDown); queueEvent(ev);
1470           ev.setType(Event::MouseUp); queueEvent(ev);
1471         }
1472         break;
1473 
1474       case INTERACTION_ID_SECONDARY_TAP:
1475       case INTERACTION_ID_HOLD:
1476         ev.setButton(Event::RightButton);
1477         ev.setType(Event::MouseMove); queueEvent(ev);
1478         ev.setType(Event::MouseDown); queueEvent(ev);
1479         ev.setType(Event::MouseUp); queueEvent(ev);
1480         break;
1481     }
1482   }
1483 }
1484 
waitTimerToConvertFingerAsMouseMovement()1485 void WinWindow::waitTimerToConvertFingerAsMouseMovement()
1486 {
1487   ASSERT(m_touch);
1488   m_touch->canBeMouse = true;
1489   clearDelayedTouchEvents();
1490   SetTimer(m_hwnd, m_touch->timerID = 1,
1491            kFingerAsMouseTimeout, nullptr);
1492   TOUCH_TRACE(" - Set timer\n");
1493 }
1494 
convertFingerAsMouseMovement()1495 void WinWindow::convertFingerAsMouseMovement()
1496 {
1497   ASSERT(m_touch);
1498   m_touch->asMouse = true;
1499   sendDelayedTouchEvents();
1500 }
1501 
delegateFingerToInteractionContext()1502 void WinWindow::delegateFingerToInteractionContext()
1503 {
1504   ASSERT(m_touch);
1505   m_touch->canBeMouse = false;
1506   m_touch->asMouse = false;
1507   clearDelayedTouchEvents();
1508   if (m_touch->timerID > 0)
1509     killTouchTimer();
1510 }
1511 
sendDelayedTouchEvents()1512 void WinWindow::sendDelayedTouchEvents()
1513 {
1514   ASSERT(m_touch);
1515   for (auto& ev : m_touch->delayedEvents)
1516     queueEvent(ev);
1517   clearDelayedTouchEvents();
1518 }
1519 
clearDelayedTouchEvents()1520 void WinWindow::clearDelayedTouchEvents()
1521 {
1522   ASSERT(m_touch);
1523   m_touch->delayedEvents.clear();
1524 }
1525 
killTouchTimer()1526 void WinWindow::killTouchTimer()
1527 {
1528   ASSERT(m_touch);
1529   if (m_touch->timerID > 0) {
1530     KillTimer(m_hwnd, m_touch->timerID);
1531     m_touch->timerID = 0;
1532     TOUCH_TRACE(" - Kill timer\n");
1533   }
1534 }
1535 
1536 //static
registerClass()1537 void WinWindow::registerClass()
1538 {
1539   HMODULE instance = GetModuleHandle(nullptr);
1540 
1541   WNDCLASSEX wcex;
1542   if (GetClassInfoEx(instance, SHE_WND_CLASS_NAME, &wcex))
1543     return;                 // Already registered
1544 
1545   wcex.cbSize        = sizeof(WNDCLASSEX);
1546   wcex.style         = CS_DBLCLKS;
1547   wcex.lpfnWndProc   = &WinWindow::staticWndProc;
1548   wcex.cbClsExtra    = 0;
1549   wcex.cbWndExtra    = 0;
1550   wcex.hInstance     = instance;
1551   wcex.hIcon         = LoadIcon(instance, L"0");
1552   wcex.hCursor       = NULL;
1553   wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
1554   wcex.lpszMenuName  = nullptr;
1555   wcex.lpszClassName = SHE_WND_CLASS_NAME;
1556   wcex.hIconSm       = nullptr;
1557 
1558   if (RegisterClassEx(&wcex) == 0)
1559     throw std::runtime_error("Error registering window class");
1560 }
1561 
1562 //static
createHwnd(WinWindow * self,int width,int height)1563 HWND WinWindow::createHwnd(WinWindow* self, int width, int height)
1564 {
1565   HWND hwnd = CreateWindowEx(
1566     WS_EX_APPWINDOW | WS_EX_ACCEPTFILES,
1567     SHE_WND_CLASS_NAME,
1568     L"",
1569     WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL,
1570     CW_USEDEFAULT, CW_USEDEFAULT,
1571     width, height,
1572     nullptr,
1573     nullptr,
1574     GetModuleHandle(nullptr),
1575     reinterpret_cast<LPVOID>(self));
1576   if (!hwnd)
1577     return nullptr;
1578 
1579   // Center the window
1580   RECT workarea;
1581   if (SystemParametersInfo(SPI_GETWORKAREA, 0, (PVOID)&workarea, 0)) {
1582     SetWindowPos(hwnd, nullptr,
1583                  (workarea.right-workarea.left)/2-width/2,
1584                  (workarea.bottom-workarea.top)/2-height/2, 0, 0,
1585                  SWP_NOSIZE |
1586                  SWP_NOSENDCHANGING |
1587                  SWP_NOOWNERZORDER |
1588                  SWP_NOZORDER |
1589                  SWP_NOREDRAW);
1590   }
1591 
1592   // Set scroll info to receive WM_HSCROLL/VSCROLL events (events
1593   // generated by some trackpad drivers).
1594   SCROLLINFO si;
1595   si.cbSize = sizeof(SCROLLINFO);
1596   si.fMask = SIF_POS | SIF_RANGE | SIF_PAGE;
1597   si.nMin = 0;
1598   si.nPos = 50;
1599   si.nMax = 100;
1600   si.nPage = 10;
1601   SetScrollInfo(hwnd, SB_HORZ, &si, FALSE);
1602   SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
1603 
1604   return hwnd;
1605 }
1606 
1607 //static
staticWndProc(HWND hwnd,UINT msg,WPARAM wparam,LPARAM lparam)1608 LRESULT CALLBACK WinWindow::staticWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
1609 {
1610   WinWindow* wnd = nullptr;
1611 
1612   if (msg == WM_CREATE) {
1613     wnd =
1614       reinterpret_cast<WinWindow*>(
1615         reinterpret_cast<LPCREATESTRUCT>(lparam)->lpCreateParams);
1616 
1617     if (wnd && wnd->m_hwnd == nullptr)
1618       wnd->m_hwnd = hwnd;
1619   }
1620   else {
1621     wnd = reinterpret_cast<WinWindow*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
1622 
1623     // Check that the user data makes sense
1624     if (wnd && wnd->m_hwnd != hwnd)
1625       wnd = nullptr;
1626   }
1627 
1628   if (wnd) {
1629     ASSERT(wnd->m_hwnd == hwnd);
1630     return wnd->wndProc(msg, wparam, lparam);
1631   }
1632   else {
1633     return DefWindowProc(hwnd, msg, wparam, lparam);
1634   }
1635 }
1636 
1637 //static
staticInteractionContextCallback(void * clientData,const INTERACTION_CONTEXT_OUTPUT * output)1638 void CALLBACK WinWindow::staticInteractionContextCallback(
1639   void* clientData,
1640   const INTERACTION_CONTEXT_OUTPUT* output)
1641 {
1642   WinWindow* self = reinterpret_cast<WinWindow*>(clientData);
1643   self->handleInteractionContextOutput(output);
1644 }
1645 
1646 // static
system()1647 WindowSystem* WinWindow::system()
1648 {
1649   return static_cast<WindowSystem*>(she::instance());
1650 }
1651 
1652 } // namespace she
1653