소스 검색

Merge pull request #14346 from RandomShaper/adpod-mt-2.1

Implement multitouch on X11 and improve it on Windows (2.1)
Rémi Verschelde 7 년 전
부모
커밋
7b97ef5d3c
6개의 변경된 파일234개의 추가작업 그리고 16개의 파일을 삭제
  1. 3 14
      platform/windows/detect.py
  2. 42 2
      platform/windows/os_windows.cpp
  3. 3 0
      platform/windows/os_windows.h
  4. 9 0
      platform/x11/detect.py
  5. 166 0
      platform/x11/os_x11.cpp
  6. 11 0
      platform/x11/os_x11.h

+ 3 - 14
platform/windows/detect.py

@@ -98,18 +98,8 @@ def is_active():
 
 def get_name():
     return "Windows"
-    if (os.getenv("MINGW32_PREFIX")):
-        mingw32=os.getenv("MINGW32_PREFIX")
-        mingw = mingw32
-    if (os.getenv("MINGW64_PREFIX")):
-        mingw64=os.getenv("MINGW64_PREFIX")
 
 
-    return [
-        ('mingw_prefix','Mingw Prefix',mingw32),
-        ('mingw_prefix_64','Mingw Prefix 64 bits',mingw64),
-    ]
-
 def can_build():
 
     if (os.name == "nt"):
@@ -176,6 +166,8 @@ def get_opts():
     return [
         ('mingw_prefix', 'Mingw Prefix', mingw32),
         ('mingw_prefix_64', 'Mingw Prefix 64 bits', mingw64),
+        # Targeted Windows version: Vista (and later)
+        ('target_win_version', 'Targeted Windows version, >= 0x0600 (Vista)', '0x0600'),
     ]
 
 
@@ -210,16 +202,13 @@ def configure(env):
 
     env.Append(CPPPATH=['#platform/windows'])
 
-    # Targeted Windows version: Vista (and later)
-    winver = "0x0600" # Windows Vista is the minimum target for windows builds
-
     env['is_mingw'] = False
     if (os.name == "nt" and os.getenv("VCINSTALLDIR")):
         # build using visual studio
         env['ENV']['TMP'] = os.environ['TMP']
         env.Append(CPPPATH=['#platform/windows/include'])
         env.Append(LIBPATH=['#platform/windows/lib'])
-        env.Append(CCFLAGS=['/DWINVER=%s' % winver, '/D_WIN32_WINNT=%s' % winver])
+        env.Append(CCFLAGS=['/DWINVER=%s' % env['target_win_version'], '/D_WIN32_WINNT=%s' % env['target_win_version']])
 
         if (env["target"] == "release"):
 

+ 42 - 2
platform/windows/os_windows.cpp

@@ -231,6 +231,18 @@ bool OS_Windows::can_draw() const {
 
 void OS_Windows::_touch_event(bool p_pressed, int p_x, int p_y, int idx) {
 
+#if WINVER >= 0x0601 // for windows 7
+	// Defensive
+	if (touch_state.has(idx) == p_pressed)
+		return;
+
+	if (p_pressed) {
+		touch_state.insert(idx, Point2i(p_x, p_y));
+	} else {
+		touch_state.erase(idx);
+	}
+#endif
+
 	InputEvent event;
 	event.type = InputEvent::SCREEN_TOUCH;
 	event.ID = ++last_id;
@@ -248,6 +260,18 @@ void OS_Windows::_touch_event(bool p_pressed, int p_x, int p_y, int idx) {
 
 void OS_Windows::_drag_event(int p_x, int p_y, int idx) {
 
+#if WINVER >= 0x0601 // for windows 7
+	Map<int, Point2i>::Element *curr = touch_state.find(idx);
+	// Defensive
+	if (!curr)
+		return;
+
+	if (curr->get() == Point2i(p_x, p_y))
+		return;
+
+	curr->get() = Point2i(p_x, p_y);
+#endif
+
 	InputEvent event;
 	event.type = InputEvent::SCREEN_DRAG;
 	event.ID = ++last_id;
@@ -292,6 +316,17 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
 			return 0; // Return To The Message Loop
 		}
 
+		case WM_KILLFOCUS: {
+
+#if WINVER >= 0x0601 // for windows 7
+			// Release every touch to avoid sticky points
+			for (Map<int, Point2i>::Element *E = touch_state.front(); E; E = E->next()) {
+				_touch_event(false, E->get().x, E->get().y, E->key());
+			}
+			touch_state.clear();
+#endif
+		} break;
+
 		case WM_PAINT:
 
 			Main::force_redraw();
@@ -682,7 +717,7 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
 							_drag_event(ti.x / 100, ti.y / 100, ti.dwID);
 						} else if (ti.dwFlags & (TOUCHEVENTF_UP | TOUCHEVENTF_DOWN)) {
 
-							_touch_event(ti.dwFlags & TOUCHEVENTF_DOWN != 0, ti.x / 100, ti.y / 100, ti.dwID);
+							_touch_event(ti.dwFlags & TOUCHEVENTF_DOWN, ti.x / 100, ti.y / 100, ti.dwID);
 						};
 					}
 					bHandled = TRUE;
@@ -1080,7 +1115,9 @@ void OS_Windows::initialize(const VideoMode &p_desired, int p_video_driver, int
 	tme.dwHoverTime = HOVER_DEFAULT;
 	TrackMouseEvent(&tme);
 
-	//RegisterTouchWindow(hWnd, 0); // Windows 7
+#if WINVER >= 0x0601 // for windows 7
+	RegisterTouchWindow(hWnd, 0); // Windows 7
+#endif
 
 	_ensure_data_dir();
 
@@ -1188,6 +1225,9 @@ void OS_Windows::finalize() {
 
 	memdelete(joystick);
 	memdelete(input);
+#if WINVER >= 0x0601 // for windows 7
+	touch_state.clear();
+#endif
 
 	visual_server->finish();
 	memdelete(visual_server);

+ 3 - 0
platform/windows/os_windows.h

@@ -130,6 +130,9 @@ class OS_Windows : public OS {
 
 	InputDefault *input;
 	joystick_windows *joystick;
+#if WINVER >= 0x0601 // for windows 7
+	Map<int, Point2i> touch_state;
+#endif
 
 #ifdef WASAPI_ENABLED
 	AudioDriverWASAPI driver_wasapi;

+ 9 - 0
platform/x11/detect.py

@@ -59,6 +59,7 @@ def get_opts():
         ('pulseaudio', 'Detect & Use pulseaudio', 'yes'),
         ('udev', 'Use udev for gamepad connection callbacks', 'no'),
         ('debug_release', 'Add debug symbols to release version', 'no'),
+        ('touch', 'Enable touch events', 'yes'),
     ]
 
 
@@ -145,6 +146,14 @@ def configure(env):
     env.ParseConfig('pkg-config xcursor --cflags --libs')
     env.ParseConfig('pkg-config xrandr --cflags --libs')
 
+    if (env['touch'] == 'yes'):
+        x11_error = os.system("pkg-config xi --modversion > /dev/null ")
+        if (x11_error):
+            print("xi not found.. cannot build with touch. Aborting.")
+            sys.exit(255)
+        env.ParseConfig('pkg-config xi --cflags --libs')
+        env.Append(CPPFLAGS=['-DTOUCH_ENABLED'])
+
     if (env['builtin_openssl'] == 'no'):
         env.ParseConfig('pkg-config openssl --cflags --libs')
 

+ 166 - 0
platform/x11/os_x11.cpp

@@ -154,6 +154,50 @@ void OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_au
 		}
 	}
 
+#ifdef TOUCH_ENABLED
+	if (!XQueryExtension(x11_display, "XInputExtension", &touch.opcode, &event_base, &error_base)) {
+		fprintf(stderr, "XInput extension not available");
+	} else {
+		// 2.2 is the first release with multitouch
+		int xi_major = 2;
+		int xi_minor = 2;
+		if (XIQueryVersion(x11_display, &xi_major, &xi_minor) != Success) {
+			fprintf(stderr, "XInput 2.2 not available (server supports %d.%d)\n", xi_major, xi_minor);
+			touch.opcode = 0;
+		} else {
+			int dev_count;
+			XIDeviceInfo *info = XIQueryDevice(x11_display, XIAllDevices, &dev_count);
+
+			for (int i = 0; i < dev_count; i++) {
+				XIDeviceInfo *dev = &info[i];
+				if (!dev->enabled)
+					continue;
+				/*if (dev->use != XIMasterPointer)
+					continue;*/
+
+				bool direct_touch = false;
+				for (int j = 0; j < dev->num_classes; j++) {
+					if (dev->classes[j]->type == XITouchClass && ((XITouchClassInfo *)dev->classes[j])->mode == XIDirectTouch) {
+						direct_touch = true;
+						printf("%d) %d %s\n", i, dev->attachment, dev->name);
+						break;
+					}
+				}
+				if (direct_touch) {
+					touch.devices.push_back(dev->deviceid);
+					fprintf(stderr, "Using touch device: %s\n", dev->name);
+				}
+			}
+
+			XIFreeDeviceInfo(info);
+
+			if (!touch.devices.size()) {
+				fprintf(stderr, "No suitable touch device found\n");
+			}
+		}
+	}
+#endif
+
 	xim = XOpenIM(x11_display, NULL, NULL, NULL);
 
 	if (xim == NULL) {
@@ -320,6 +364,32 @@ void OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_au
 
 	XChangeWindowAttributes(x11_display, x11_window, CWEventMask, &new_attr);
 
+#ifdef TOUCH_ENABLED
+	if (touch.devices.size()) {
+
+		// Must be alive after this block
+		static unsigned char mask_data[XIMaskLen(XI_LASTEVENT)] = {};
+
+		touch.event_mask.deviceid = XIAllMasterDevices;
+		touch.event_mask.mask_len = sizeof(mask_data);
+		touch.event_mask.mask = mask_data;
+
+		XISetMask(touch.event_mask.mask, XI_TouchBegin);
+		XISetMask(touch.event_mask.mask, XI_TouchUpdate);
+		XISetMask(touch.event_mask.mask, XI_TouchEnd);
+		XISetMask(touch.event_mask.mask, XI_TouchOwnership);
+
+		XISelectEvents(x11_display, x11_window, &touch.event_mask, 1);
+
+		XIClearMask(touch.event_mask.mask, XI_TouchOwnership);
+
+		// Grab touch devices to avoid OS gesture interference
+		for (int i = 0; i < touch.devices.size(); ++i) {
+			XIGrabDevice(x11_display, touch.devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &touch.event_mask);
+		}
+	}
+#endif
+
 	XClassHint *classHint;
 
 	/* set the titlebar name */
@@ -473,6 +543,10 @@ void OS_X11::finalize() {
 
 #ifdef JOYDEV_ENABLED
 	memdelete(joystick);
+#endif
+#ifdef TOUCH_ENABLED
+	touch.devices.clear();
+	touch.state.clear();
 #endif
 	memdelete(input);
 
@@ -1294,6 +1368,73 @@ void OS_X11::process_xevents() {
 		XEvent event;
 		XNextEvent(x11_display, &event);
 
+#ifdef TOUCH_ENABLED
+		if (XGetEventData(x11_display, &event.xcookie)) {
+
+			if (event.xcookie.extension == touch.opcode) {
+
+				InputEvent input_event;
+				input_event.ID = ++event_id;
+				input_event.device = 0;
+
+				XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data;
+				int index = event_data->detail;
+				Point2i pos = Point2i(event_data->event_x, event_data->event_y);
+
+				switch (event_data->evtype) {
+
+					case XI_TouchBegin: // Fall-through
+						XIAllowTouchEvents(x11_display, event_data->deviceid, event_data->detail, x11_window, XIAcceptTouch);
+
+					case XI_TouchEnd: {
+
+						bool is_begin = event_data->evtype == XI_TouchBegin;
+
+						input_event.type = InputEvent::SCREEN_TOUCH;
+						input_event.screen_touch.index = index;
+						input_event.screen_touch.x = pos.x;
+						input_event.screen_touch.y = pos.y;
+						input_event.screen_touch.pressed = is_begin;
+
+						if (is_begin) {
+							if (touch.state.has(index)) // Defensive
+								break;
+							touch.state[index] = pos;
+							input->parse_input_event(input_event);
+						} else {
+							if (!touch.state.has(index)) // Defensive
+								break;
+							touch.state.erase(index);
+							input->parse_input_event(input_event);
+						}
+					} break;
+
+					case XI_TouchUpdate: {
+
+						Map<int, Point2i>::Element *curr_pos_elem = touch.state.find(index);
+						if (!curr_pos_elem) // Defensive
+							break;
+
+						if (curr_pos_elem->value() != pos) {
+
+							input_event.type = InputEvent::SCREEN_DRAG;
+							input_event.screen_drag.index = index;
+							input_event.screen_drag.x = pos.x;
+							input_event.screen_drag.y = pos.y;
+							input_event.screen_drag.relative_x = pos.x - curr_pos_elem->value().x;
+							input_event.screen_drag.relative_y = pos.y - curr_pos_elem->value().y;
+							input->parse_input_event(input_event);
+
+							curr_pos_elem->value() = pos;
+						}
+					} break;
+				}
+			}
+
+			XFreeEventData(x11_display, &event.xcookie);
+		}
+#endif
+
 		switch (event.type) {
 			case Expose:
 				Main::force_redraw();
@@ -1331,6 +1472,12 @@ void OS_X11::process_xevents() {
 							ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
 							GrabModeAsync, GrabModeAsync, x11_window, None, CurrentTime);
 				}
+#ifdef TOUCH_ENABLED
+				// Grab touch devices to avoid OS gesture interference
+				for (int i = 0; i < touch.devices.size(); ++i) {
+					XIGrabDevice(x11_display, touch.devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &touch.event_mask);
+				}
+#endif
 				break;
 
 			case FocusOut:
@@ -1339,6 +1486,25 @@ void OS_X11::process_xevents() {
 					//dear X11, I try, I really try, but you never work, you do whathever you want.
 					XUngrabPointer(x11_display, CurrentTime);
 				}
+#ifdef TOUCH_ENABLED
+				// Ungrab touch devices so input works as usual while we are unfocused
+				for (int i = 0; i < touch.devices.size(); ++i) {
+					XIUngrabDevice(x11_display, touch.devices[i], CurrentTime);
+				}
+
+				// Release every pointer to avoid sticky points
+				for (Map<int, Point2i>::Element *E = touch.state.front(); E; E = E->next()) {
+
+					InputEvent input_event;
+					input_event.type = InputEvent::SCREEN_TOUCH;
+					input_event.screen_touch.index = E->key();
+					input_event.screen_touch.x = E->get().x;
+					input_event.screen_touch.y = E->get().y;
+					input_event.screen_touch.pressed = false;
+					input->parse_input_event(input_event);
+				}
+				touch.state.clear();
+#endif
 				break;
 
 			case ConfigureNotify:

+ 11 - 0
platform/x11/os_x11.h

@@ -55,6 +55,9 @@
 #include <X11/Xlib.h>
 #include <X11/extensions/Xrandr.h>
 #include <X11/keysym.h>
+#ifdef TOUCH_ENABLED
+#include <X11/extensions/XInput2.h>
+#endif
 
 // Hints for X11 fullscreen
 typedef struct {
@@ -122,6 +125,14 @@ class OS_X11 : public OS_Unix {
 	uint64_t last_click_ms;
 	unsigned int event_id;
 	uint32_t last_button_state;
+#ifdef TOUCH_ENABLED
+	struct {
+		int opcode;
+		Vector<int> devices;
+		XIEventMask event_mask;
+		Map<int, Point2i> state;
+	} touch;
+#endif
 
 	PhysicsServer *physics_server;
 	unsigned int get_mouse_button_state(unsigned int p_x11_state);