Bladeren bron

[Windows] Add support for the WinTab API for pen input.

bruvzg 5 jaren geleden
bovenliggende
commit
d978658f81

+ 1 - 1
core/input/input_event.cpp

@@ -708,7 +708,7 @@ String InputEventMouseMotion::as_text() const {
 			button_mask_string = itos(get_button_mask());
 			break;
 	}
-	return "InputEventMouseMotion : button_mask=" + button_mask_string + ", position=(" + String(get_position()) + "), relative=(" + String(get_relative()) + "), speed=(" + String(get_speed()) + ")";
+	return "InputEventMouseMotion : button_mask=" + button_mask_string + ", position=(" + String(get_position()) + "), relative=(" + String(get_relative()) + "), speed=(" + String(get_speed()) + "), pressure=(" + rtos(get_pressure()) + "), tilt=(" + String(get_tilt()) + ")";
 }
 
 bool InputEventMouseMotion::accumulate(const Ref<InputEvent> &p_event) {

+ 2 - 0
core/os/os.h

@@ -59,6 +59,7 @@ class OS {
 	bool _allow_layered;
 	bool _use_vsync;
 	bool _vsync_via_compositor;
+	bool _disable_wintab;
 
 	char *last_error;
 
@@ -148,6 +149,7 @@ public:
 
 	bool is_layered_allowed() const { return _allow_layered; }
 	bool is_hidpi_allowed() const { return _allow_hidpi; }
+	bool is_wintab_disabled() const { return _disable_wintab; }
 
 	void ensure_user_data_dir();
 

+ 3 - 0
doc/classes/ProjectSettings.xml

@@ -414,6 +414,9 @@
 		<member name="display/mouse_cursor/tooltip_position_offset" type="Vector2" setter="" getter="" default="Vector2( 10, 10 )">
 			Position offset for tooltips, relative to the mouse cursor's hotspot.
 		</member>
+		<member name="display/window/disable_wintab_api" type="bool" setter="" getter="" default="false">
+		    Disables WinTab API and always use Windows Ink API for the pen input (Windows only).
+		</member>
 		<member name="display/window/dpi/allow_hidpi" type="bool" setter="" getter="" default="false">
 			If [code]true[/code], allows HiDPI display on Windows and macOS. This setting has no effect on desktop Linux, as DPI-awareness fallbacks are not supported there.
 		</member>

+ 12 - 0
main/main.cpp

@@ -139,6 +139,7 @@ static DisplayServer::ScreenOrientation window_orientation = DisplayServer::SCRE
 static uint32_t window_flags = 0;
 static Size2i window_size = Size2i(1024, 600);
 static bool window_vsync_via_compositor = false;
+static bool disable_wintab = false;
 
 static int init_screen = -1;
 static bool init_fullscreen = false;
@@ -314,6 +315,7 @@ void Main::print_help(const char *p_binary) {
 	OS::get_singleton()->print("  --enable-vsync-via-compositor    When vsync is enabled, vsync via the OS' window compositor (Windows only).\n");
 	OS::get_singleton()->print("  --disable-vsync-via-compositor   Disable vsync via the OS' window compositor (Windows only).\n");
 	OS::get_singleton()->print("  --single-window                  Use a single window (no separate subwindows).\n");
+	OS::get_singleton()->print("  --disable-wintab                 Disable WinTab API and always use Windows Ink API for the pen input (Windows only).\n");
 	OS::get_singleton()->print("\n");
 #endif
 
@@ -595,6 +597,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
 		} else if (I->get() == "--gpu-abort") { // force windowed window
 
 			Engine::singleton->abort_on_gpu_errors = true;
+		} else if (I->get() == "--disable-wintab") {
+
+			disable_wintab = true;
 		} else if (I->get() == "--single-window") { // force single window
 
 			single_window = true;
@@ -1079,6 +1084,13 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
 
 	OS::get_singleton()->_vsync_via_compositor = window_vsync_via_compositor;
 
+	if (!disable_wintab) {
+		// No "--disable_wintab" option
+		disable_wintab = GLOBAL_DEF("display/window/disable_wintab_api", false);
+	}
+
+	OS::get_singleton()->_disable_wintab = disable_wintab;
+
 	/* todo restore
 	OS::get_singleton()->_allow_layered = GLOBAL_DEF("display/window/per_pixel_transparency/allowed", false);
 	video_mode.layered = GLOBAL_DEF("display/window/per_pixel_transparency/enabled", false);

+ 120 - 8
platform/windows/display_server_windows.cpp

@@ -36,10 +36,6 @@
 
 #include <avrt.h>
 
-#ifndef WM_POINTERUPDATE
-#define WM_POINTERUPDATE 0x0245
-#endif
-
 #ifdef DEBUG_ENABLED
 static String format_error_message(DWORD id) {
 
@@ -545,6 +541,10 @@ void DisplayServerWindows::delete_sub_window(WindowID p_window) {
 	}
 #endif
 
+	if (!OS::get_singleton()->is_wintab_disabled() && wintab_available && windows[p_window].wtctx) {
+		wintab_WTClose(windows[p_window].wtctx);
+		windows[p_window].wtctx = 0;
+	}
 	DestroyWindow(windows[p_window].hWnd);
 	windows.erase(p_window);
 }
@@ -1849,7 +1849,11 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
 				alt_mem = false;
 			};
 
-			return 0; // Return To The Message Loop
+			if (!OS::get_singleton()->is_wintab_disabled() && wintab_available && windows[window_id].wtctx) {
+				wintab_WTEnable(windows[window_id].wtctx, GET_WM_ACTIVATE_STATE(wParam, lParam));
+			}
+
+			return 0; // Return  To The Message Loop
 		}
 		case WM_GETMINMAXINFO: {
 			if (windows[window_id].resizable && !windows[window_id].fullscreen) {
@@ -1928,6 +1932,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
 				mm->set_control(control_mem);
 				mm->set_shift(shift_mem);
 				mm->set_alt(alt_mem);
+
 				mm->set_pressure((raw->data.mouse.ulButtons & RI_MOUSE_LEFT_BUTTON_DOWN) ? 1.0f : 0.0f);
 
 				mm->set_button_mask(last_button_state);
@@ -1978,6 +1983,42 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
 			}
 			delete[] lpb;
 		} break;
+		case WT_CSRCHANGE:
+		case WT_PROXIMITY: {
+			if (!OS::get_singleton()->is_wintab_disabled() && wintab_available && windows[window_id].wtctx) {
+				AXIS pressure;
+				if (wintab_WTInfo(WTI_DEVICES + windows[window_id].wtlc.lcDevice, DVC_NPRESSURE, &pressure)) {
+					windows[window_id].min_pressure = int(pressure.axMin);
+					windows[window_id].max_pressure = int(pressure.axMax);
+				}
+				AXIS orientation[3];
+				if (wintab_WTInfo(WTI_DEVICES + windows[window_id].wtlc.lcDevice, DVC_ORIENTATION, &orientation)) {
+					windows[window_id].tilt_supported = orientation[0].axResolution && orientation[1].axResolution;
+				}
+				return 0;
+			}
+		} break;
+		case WT_PACKET: {
+			if (!OS::get_singleton()->is_wintab_disabled() && wintab_available && windows[window_id].wtctx) {
+				PACKET packet;
+				if (wintab_WTPacket(windows[window_id].wtctx, wParam, &packet)) {
+
+					float pressure = float(packet.pkNormalPressure - windows[window_id].min_pressure) / float(windows[window_id].max_pressure - windows[window_id].min_pressure);
+					windows[window_id].last_pressure = pressure;
+					windows[window_id].last_pressure_update = 0;
+
+					double azim = (packet.pkOrientation.orAzimuth / 10.0f) * (Math_PI / 180);
+					double alt = Math::tan((Math::abs(packet.pkOrientation.orAltitude / 10.0f)) * (Math_PI / 180));
+
+					if (windows[window_id].tilt_supported) {
+						windows[window_id].last_tilt = Vector2(Math::atan(Math::sin(azim) / alt), Math::atan(Math::cos(azim) / alt));
+					} else {
+						windows[window_id].last_tilt = Vector2();
+					}
+				}
+				return 0;
+			}
+		} break;
 		case WM_POINTERUPDATE: {
 			if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) {
 				break;
@@ -2145,7 +2186,21 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
 			mm->set_shift((wParam & MK_SHIFT) != 0);
 			mm->set_alt(alt_mem);
 
-			mm->set_pressure((wParam & MK_LBUTTON) ? 1.0f : 0.0f);
+			if (!OS::get_singleton()->is_wintab_disabled() && wintab_available && windows[window_id].wtctx) {
+				// Note: WinTab sends both WT_PACKET and WM_xBUTTONDOWN/UP/MOUSEMOVE events, use mouse 1/0 pressure only when last_pressure was not update recently.
+				if (windows[window_id].last_pressure_update < 10) {
+					windows[window_id].last_pressure_update++;
+				} else {
+					windows[window_id].last_tilt = Vector2();
+					windows[window_id].last_pressure = (wParam & MK_LBUTTON) ? 1.0f : 0.0f;
+				}
+			} else {
+				windows[window_id].last_tilt = Vector2();
+				windows[window_id].last_pressure = (wParam & MK_LBUTTON) ? 1.0f : 0.0f;
+			}
+
+			mm->set_pressure(windows[window_id].last_pressure);
+			mm->set_tilt(windows[window_id].last_tilt);
 
 			mm->set_button_mask(last_button_state);
 
@@ -2768,6 +2823,39 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
 
 		DragAcceptFiles(wd.hWnd, true);
 
+		if (!OS::get_singleton()->is_wintab_disabled() && wintab_available) {
+			wintab_WTInfo(WTI_DEFSYSCTX, 0, &wd.wtlc);
+			wd.wtlc.lcOptions |= CXO_MESSAGES;
+			wd.wtlc.lcPktData = PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE | PK_ORIENTATION;
+			wd.wtlc.lcMoveMask = PK_NORMAL_PRESSURE | PK_TANGENT_PRESSURE;
+			wd.wtlc.lcPktMode = 0;
+			wd.wtlc.lcOutOrgX = 0;
+			wd.wtlc.lcOutExtX = wd.wtlc.lcInExtX;
+			wd.wtlc.lcOutOrgY = 0;
+			wd.wtlc.lcOutExtY = -wd.wtlc.lcInExtY;
+			wd.wtctx = wintab_WTOpen(wd.hWnd, &wd.wtlc, false);
+			if (wd.wtctx) {
+				wintab_WTEnable(wd.wtctx, true);
+				AXIS pressure;
+				if (wintab_WTInfo(WTI_DEVICES + wd.wtlc.lcDevice, DVC_NPRESSURE, &pressure)) {
+					wd.min_pressure = int(pressure.axMin);
+					wd.max_pressure = int(pressure.axMax);
+				}
+				AXIS orientation[3];
+				if (wintab_WTInfo(WTI_DEVICES + wd.wtlc.lcDevice, DVC_ORIENTATION, &orientation)) {
+					wd.tilt_supported = orientation[0].axResolution && orientation[1].axResolution;
+				}
+			} else {
+				ERR_PRINT("WinTab context creation falied.");
+			}
+		} else {
+			wd.wtctx = 0;
+		}
+
+		wd.last_pressure = 0;
+		wd.last_pressure_update = 0;
+		wd.last_tilt = Vector2();
+
 		// IME
 		wd.im_himc = ImmGetContext(wd.hWnd);
 		ImmReleaseContext(wd.hWnd, wd.im_himc);
@@ -2785,6 +2873,15 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
 	return id;
 }
 
+// WinTab API
+bool DisplayServerWindows::wintab_available = false;
+WTOpenPtr DisplayServerWindows::wintab_WTOpen = nullptr;
+WTClosePtr DisplayServerWindows::wintab_WTClose = nullptr;
+WTInfoPtr DisplayServerWindows::wintab_WTInfo = nullptr;
+WTPacketPtr DisplayServerWindows::wintab_WTPacket = nullptr;
+WTEnablePtr DisplayServerWindows::wintab_WTEnable = nullptr;
+
+// Windows Ink API
 GetPointerTypePtr DisplayServerWindows::win8p_GetPointerType = nullptr;
 GetPointerPenInfoPtr DisplayServerWindows::win8p_GetPointerPenInfo = nullptr;
 
@@ -2796,7 +2893,19 @@ typedef enum _SHC_PROCESS_DPI_AWARENESS {
 
 DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
 
-	//Note: Functions for pen input, available on Windows 8+
+	//Note: Wacom WinTab driver API for pen input, for devices incompatible with Windows Ink.
+	HMODULE wintab_lib = LoadLibraryW(L"wintab32.dll");
+	if (wintab_lib) {
+		wintab_WTOpen = (WTOpenPtr)GetProcAddress(wintab_lib, "WTOpenW");
+		wintab_WTClose = (WTClosePtr)GetProcAddress(wintab_lib, "WTClose");
+		wintab_WTInfo = (WTInfoPtr)GetProcAddress(wintab_lib, "WTInfoW");
+		wintab_WTPacket = (WTPacketPtr)GetProcAddress(wintab_lib, "WTPacket");
+		wintab_WTEnable = (WTEnablePtr)GetProcAddress(wintab_lib, "WTEnable");
+
+		wintab_available = wintab_WTOpen && wintab_WTClose && wintab_WTInfo && wintab_WTPacket && wintab_WTEnable;
+	}
+
+	//Note: Windows Ink API for pen input, available on Windows 8+ only.
 	HMODULE user32_lib = LoadLibraryW(L"user32.dll");
 	if (user32_lib) {
 		win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType");
@@ -3013,7 +3122,10 @@ DisplayServerWindows::~DisplayServerWindows() {
 			context_vulkan->window_destroy(MAIN_WINDOW_ID);
 		}
 #endif
-
+		if (wintab_available && windows[MAIN_WINDOW_ID].wtctx) {
+			wintab_WTClose(windows[MAIN_WINDOW_ID].wtctx);
+			windows[MAIN_WINDOW_ID].wtctx = 0;
+		}
 		DestroyWindow(windows[MAIN_WINDOW_ID].hWnd);
 	}
 }

+ 104 - 0
platform/windows/display_server_windows.h

@@ -66,6 +66,87 @@
 #include <windows.h>
 #include <windowsx.h>
 
+// WinTab API
+#define WT_PACKET 0x7FF0
+#define WT_PROXIMITY 0x7FF5
+#define WT_INFOCHANGE 0x7FF6
+#define WT_CSRCHANGE 0x7FF7
+
+#define WTI_DEFSYSCTX 4
+#define WTI_DEVICES 100
+#define DVC_NPRESSURE 15
+#define DVC_TPRESSURE 16
+#define DVC_ORIENTATION 17
+#define DVC_ROTATION 18
+
+#define CXO_MESSAGES 0x0004
+#define PK_NORMAL_PRESSURE 0x0400
+#define PK_TANGENT_PRESSURE 0x0800
+#define PK_ORIENTATION 0x1000
+
+typedef struct tagLOGCONTEXTW {
+	WCHAR lcName[40];
+	UINT lcOptions;
+	UINT lcStatus;
+	UINT lcLocks;
+	UINT lcMsgBase;
+	UINT lcDevice;
+	UINT lcPktRate;
+	DWORD lcPktData;
+	DWORD lcPktMode;
+	DWORD lcMoveMask;
+	DWORD lcBtnDnMask;
+	DWORD lcBtnUpMask;
+	LONG lcInOrgX;
+	LONG lcInOrgY;
+	LONG lcInOrgZ;
+	LONG lcInExtX;
+	LONG lcInExtY;
+	LONG lcInExtZ;
+	LONG lcOutOrgX;
+	LONG lcOutOrgY;
+	LONG lcOutOrgZ;
+	LONG lcOutExtX;
+	LONG lcOutExtY;
+	LONG lcOutExtZ;
+	DWORD lcSensX;
+	DWORD lcSensY;
+	DWORD lcSensZ;
+	BOOL lcSysMode;
+	int lcSysOrgX;
+	int lcSysOrgY;
+	int lcSysExtX;
+	int lcSysExtY;
+	DWORD lcSysSensX;
+	DWORD lcSysSensY;
+} LOGCONTEXTW;
+
+typedef struct tagAXIS {
+	LONG axMin;
+	LONG axMax;
+	UINT axUnits;
+	DWORD axResolution;
+} AXIS;
+
+typedef struct tagORIENTATION {
+	int orAzimuth;
+	int orAltitude;
+	int orTwist;
+} ORIENTATION;
+
+typedef struct tagPACKET {
+	int pkNormalPressure;
+	int pkTangentPressure;
+	ORIENTATION pkOrientation;
+} PACKET;
+
+typedef HANDLE(WINAPI *WTOpenPtr)(HWND p_window, LOGCONTEXTW *p_ctx, BOOL p_enable);
+typedef BOOL(WINAPI *WTClosePtr)(HANDLE p_ctx);
+typedef UINT(WINAPI *WTInfoPtr)(UINT p_category, UINT p_index, LPVOID p_output);
+typedef BOOL(WINAPI *WTPacketPtr)(HANDLE p_ctx, UINT p_param, LPVOID p_packets);
+typedef BOOL(WINAPI *WTEnablePtr)(HANDLE p_ctx, BOOL p_enable);
+
+// Windows Ink API
 #ifndef POINTER_STRUCTURES
 
 #define POINTER_STRUCTURES
@@ -144,6 +225,10 @@ typedef struct tagPOINTER_PEN_INFO {
 
 #endif //POINTER_STRUCTURES
 
+#ifndef WM_POINTERUPDATE
+#define WM_POINTERUPDATE 0x0245
+#endif
+
 typedef BOOL(WINAPI *GetPointerTypePtr)(uint32_t p_id, POINTER_INPUT_TYPE *p_type);
 typedef BOOL(WINAPI *GetPointerPenInfoPtr)(uint32_t p_id, POINTER_PEN_INFO *p_pen_info);
 
@@ -171,6 +256,15 @@ class DisplayServerWindows : public DisplayServer {
 
 	_THREAD_SAFE_CLASS_
 
+	// WinTab API
+	static bool wintab_available;
+	static WTOpenPtr wintab_WTOpen;
+	static WTClosePtr wintab_WTClose;
+	static WTInfoPtr wintab_WTInfo;
+	static WTPacketPtr wintab_WTPacket;
+	static WTEnablePtr wintab_WTEnable;
+
+	// Windows Ink API
 	static GetPointerTypePtr win8p_GetPointerType;
 	static GetPointerPenInfoPtr win8p_GetPointerPenInfo;
 
@@ -230,6 +324,16 @@ class DisplayServerWindows : public DisplayServer {
 		bool no_focus = false;
 		bool window_has_focus = false;
 
+		HANDLE wtctx;
+		LOGCONTEXTW wtlc;
+		int min_pressure;
+		int max_pressure;
+		bool tilt_supported;
+
+		int last_pressure_update;
+		float last_pressure;
+		Vector2 last_tilt;
+
 		HBITMAP hBitmap; //DIB section for layered window
 		uint8_t *dib_data = nullptr;
 		Size2 dib_size;