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