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 #ifndef SHE_WIN_WINDOW_H_INCLUDED
8 #define SHE_WIN_WINDOW_H_INCLUDED
9 #pragma once
10 
11 #include "base/time.h"
12 #include "gfx/size.h"
13 #include "she/event.h"
14 #include "she/native_cursor.h"
15 #include "she/pointer_type.h"
16 #include "she/win/pen.h"
17 
18 #include <string>
19 #include <windows.h>
20 #include <interactioncontext.h>
21 
22 #define SHE_USE_POINTER_API_FOR_MOUSE 0
23 
24 namespace she {
25   class Surface;
26   class WindowSystem;
27 
28   class WinWindow {
29   public:
30     WinWindow(int width, int height, int scale);
31     ~WinWindow();
32 
33     void queueEvent(Event& ev);
scale()34     int scale() const { return m_scale; }
35     void setScale(int scale);
36     void setVisible(bool visible);
37     void maximize();
38     bool isMaximized() const;
39     bool isMinimized() const;
40     gfx::Size clientSize() const;
41     gfx::Size restoredSize() const;
42     void setTitle(const std::string& title);
43     void captureMouse();
44     void releaseMouse();
45     void setMousePosition(const gfx::Point& position);
46     bool setNativeMouseCursor(NativeCursor cursor);
47     bool setNativeMouseCursor(const she::Surface* surface,
48                               const gfx::Point& focus,
49                               const int scale);
50     void updateWindow(const gfx::Rect& bounds);
51     std::string getLayout();
52 
53     void setLayout(const std::string& layout);
54     void setTranslateDeadKeys(bool state);
55     void setInterpretOneFingerGestureAsMouseMovement(bool state);
56 
handle()57     HWND handle() { return m_hwnd; }
58 
59   private:
60     bool setCursor(HCURSOR hcursor, bool custom);
61     LRESULT wndProc(UINT msg, WPARAM wparam, LPARAM lparam);
62     void mouseEvent(LPARAM lparam, Event& ev);
63     bool pointerEvent(WPARAM wparam, Event& ev, POINTER_INFO& pi);
64     void handlePointerButtonChange(Event& ev, POINTER_INFO& pi);
65     void handleInteractionContextOutput(
66       const INTERACTION_CONTEXT_OUTPUT* output);
67 
68     void waitTimerToConvertFingerAsMouseMovement();
69     void convertFingerAsMouseMovement();
70     void delegateFingerToInteractionContext();
71     void sendDelayedTouchEvents();
72     void clearDelayedTouchEvents();
73     void killTouchTimer();
74 
onQueueEvent(Event & ev)75     virtual void onQueueEvent(Event& ev) { }
onResize(const gfx::Size & sz)76     virtual void onResize(const gfx::Size& sz) { }
onPaint(HDC hdc)77     virtual void onPaint(HDC hdc) { }
78 
79     static void registerClass();
80     static HWND createHwnd(WinWindow* self, int width, int height);
81     static LRESULT CALLBACK staticWndProc(
82       HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
83     static void CALLBACK staticInteractionContextCallback(
84       void* clientData,
85       const INTERACTION_CONTEXT_OUTPUT* output);
86 
87     static WindowSystem* system();
88 
89     mutable HWND m_hwnd;
90     HCURSOR m_hcursor;
91     gfx::Size m_clientSize;
92     gfx::Size m_restoredSize;
93     int m_scale;
94     bool m_isCreated;
95     bool m_translateDeadKeys;
96     bool m_hasMouse;
97     bool m_captureMouse;
98     bool m_customHcursor;
99 
100     // Windows 8 pointer API
101     bool m_usePointerApi;
102     UINT32 m_lastPointerId;
103     UINT32 m_capturePointerId;
104     HINTERACTIONCONTEXT m_ictx;
105 
106     // This might be the most ugliest hack I've done to fix a Windows
107     // bug. Here's the thing:
108     // 1) When we use the pen on a Surface device, it send us
109     //    WM_POINTERUPDATE messages, but after a WM_POINTERUPDATE
110     //    we'll receive a WM_MOUSEMOVE with the position of the mouse
111     //    (which is different to the position of the pen). This
112     //    happens constantly if we press a modifier key like Alt,
113     //    Ctrl, or Shift, and randomly if we don't use a modifier.
114     // 2) First I tried to fix this issue disabling the WM_MOUSEMOVE
115     //    processing when a pointer device is inside the window (with
116     //    WM_POINTERENTER/LEAVE events), but that generated some buggy
117     //    behavior (sometimes we stopped processing WM_MOUSEMOVE
118     //    messages completely, it looks like a WM_POINTERLEAVE could
119     //    be lost in time).
120     // 3) Then I tried to fix this using the Windows pointer API for
121     //    mouse messages (EnableMouseInPointer(TRUE)) but that API
122     //    doesn't work well with Wacom drivers. And I guess it might
123     //    depend on the specific driver version.
124     // 4) Now I've reverted the pointer API change (we keep using the
125     //    WM_MOUSE* messages) and fixed the original issue with this
126     //    "m_ignoreRandomMouseEvents" variable. This is a counter that
127     //    indicates the number of WM_MOUSEMOVE messages that we must
128     //    ignore after a WM_POINTERUPDATE.
129     // 5) Each time we receive a WM_POINTERUPDATE this counter is set
130     //    to 2. A value that I calculated practically just testing the
131     //    pen in a Surface Pro using the Alt modifier and seeing how
132     //    many random WM_MOUSEMOVE messages were received.
133     // 6) When a WM_MOUSEMOVE is received this counter is decreased,
134     //    so when it backs to 0 we start processing WM_MOUSEMOVE
135     //    messages again.
136     int m_ignoreRandomMouseEvents;
137 
138     // Variables used to convert one finger in mouse-like movement,
139     // and two/more fingers in scroll movement/pan/zoom. The idea
140     // is as follows:
141     // 1) When a PT_TOUCH is received, we count this event in
142     //    m_fingers and setup a timer (m_fingerTimerID) to wait for
143     //    another touch event.
144     // 2) If another touch event is received, we process the messages
145     //    with the interaction context (m_ictx) to handle special
146     //    gestures (pan, magnify, etc.).
147     // 3) If the timeout is reached, and we've received only one
148     //    finger on the windows, we can send all awaiting events
149     //    (m_fingerEvents) like mouse movement messages/button
150     //    presses/releases.
151     struct Touch {
152       int fingers;              // Number of fingers in the window
153       // True when the timeout wasn't reached yet and the finger can be
154       // converted to mouse events yet.
155       bool canBeMouse;
156       // True if we're already processing finger/touch events as mouse
157       // movement events.
158       bool asMouse;
159       // Timeout (WM_TIMER) when the finger is converted to mouse events.
160       UINT_PTR timerID;
161       // Queued events to be sent when the finger is converted to mouse
162       // events (these events are discarded if another finger is
163       // introduced in the gesture e.g. to pan)
164       std::vector<Event> delayedEvents;
165       Touch();
166     } *m_touch;
167 
168 #if SHE_USE_POINTER_API_FOR_MOUSE
169     // Emulate double-click with pointer API. I guess that this should
170     // be done by the Interaction Context API but it looks like
171     // messages with pointerType != PT_TOUCH or PT_PEN are just
172     // ignored by the ProcessPointerFramesInteractionContext()
173     // function even when we call AddPointerInteractionContext() with
174     // the given PT_MOUSE pointer.
175     bool m_emulateDoubleClick;
176     base::tick_t m_doubleClickMsecs;
177     base::tick_t m_lastPointerDownTime;
178     Event::MouseButton m_lastPointerDownButton;
179     int m_pointerDownCount;
180 #endif
181 
182     // Wintab API data
183     HCTX m_hpenctx;
184     PointerType m_pointerType;
185     double m_pressure;
186   };
187 
188 } // namespace she
189 
190 #endif
191