Browse Source

Add opt-in support for trackpad touch events. Add touch device types.

- Add new touch device type enum ("touchscreen", "touchpad", "touchpadrelative"). Coordinates for touchscreens are window-relative DPI-scaled pixels, and coordinates for touchpads are normalized [0, 1] values.
- Add new devicetype enum and ismouse boolean params to touch callbacks.
- Add love.touch.getDeviceType(touchid), love.touch.isMouse(touchid), and optional device type filter param for love.touch.getTouches.
- Add t.trackpadtouch boolean (defaults to false) to love.conf. Trackpad touch events will not be generated unless it's enabled.

Currently the trackpadtouch setting only works on macOS.

Resolves #1633.
Resolves #2093.
Sasha Szpakowski 7 months ago
parent
commit
639580cbd1

+ 1 - 0
CMakeLists.txt

@@ -1182,6 +1182,7 @@ target_link_libraries(love_timer PUBLIC
 #
 #
 
 
 add_library(love_touch_root STATIC
 add_library(love_touch_root STATIC
+	src/modules/touch/Touch.cpp
 	src/modules/touch/Touch.h
 	src/modules/touch/Touch.h
 	src/modules/touch/wrap_Touch.cpp
 	src/modules/touch/wrap_Touch.cpp
 	src/modules/touch/wrap_Touch.h
 	src/modules/touch/wrap_Touch.h

+ 7 - 1
platform/xcode/liblove.xcodeproj/project.pbxproj

@@ -51,6 +51,8 @@
 		217DFC111D9F6D490055D849 /* usocket.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBD51D9F6D490055D849 /* usocket.c */; };
 		217DFC111D9F6D490055D849 /* usocket.c in Sources */ = {isa = PBXBuildFile; fileRef = 217DFBD51D9F6D490055D849 /* usocket.c */; };
 		217DFC121D9F6D490055D849 /* usocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 217DFBD61D9F6D490055D849 /* usocket.h */; };
 		217DFC121D9F6D490055D849 /* usocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 217DFBD61D9F6D490055D849 /* usocket.h */; };
 		D923E7D3296B85B9002FF1B3 /* harfbuzz.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = D923E7D2296B85B9002FF1B3 /* harfbuzz.xcframework */; };
 		D923E7D3296B85B9002FF1B3 /* harfbuzz.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = D923E7D2296B85B9002FF1B3 /* harfbuzz.xcframework */; };
+		D93660F82D1C727C00C0EC4B /* Touch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D93660F72D1C727C00C0EC4B /* Touch.cpp */; };
+		D93660F92D1C727C00C0EC4B /* Touch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D93660F72D1C727C00C0EC4B /* Touch.cpp */; };
 		D943E58E2A24D56000D80361 /* PhysfsIo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D943E58C2A24D56000D80361 /* PhysfsIo.cpp */; };
 		D943E58E2A24D56000D80361 /* PhysfsIo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D943E58C2A24D56000D80361 /* PhysfsIo.cpp */; };
 		D943E58F2A24D56000D80361 /* PhysfsIo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D943E58C2A24D56000D80361 /* PhysfsIo.cpp */; };
 		D943E58F2A24D56000D80361 /* PhysfsIo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D943E58C2A24D56000D80361 /* PhysfsIo.cpp */; };
 		D943E5902A24D56000D80361 /* PhysfsIo.h in Headers */ = {isa = PBXBuildFile; fileRef = D943E58D2A24D56000D80361 /* PhysfsIo.h */; };
 		D943E5902A24D56000D80361 /* PhysfsIo.h in Headers */ = {isa = PBXBuildFile; fileRef = D943E58D2A24D56000D80361 /* PhysfsIo.h */; };
@@ -1413,6 +1415,7 @@
 		217DFBD51D9F6D490055D849 /* usocket.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = usocket.c; sourceTree = "<group>"; };
 		217DFBD51D9F6D490055D849 /* usocket.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = usocket.c; sourceTree = "<group>"; };
 		217DFBD61D9F6D490055D849 /* usocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = usocket.h; sourceTree = "<group>"; };
 		217DFBD61D9F6D490055D849 /* usocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = usocket.h; sourceTree = "<group>"; };
 		D923E7D2296B85B9002FF1B3 /* harfbuzz.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = harfbuzz.xcframework; path = ios/libraries/harfbuzz.xcframework; sourceTree = "<group>"; };
 		D923E7D2296B85B9002FF1B3 /* harfbuzz.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = harfbuzz.xcframework; path = ios/libraries/harfbuzz.xcframework; sourceTree = "<group>"; };
+		D93660F72D1C727C00C0EC4B /* Touch.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Touch.cpp; sourceTree = "<group>"; };
 		D943E58C2A24D56000D80361 /* PhysfsIo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PhysfsIo.cpp; sourceTree = "<group>"; };
 		D943E58C2A24D56000D80361 /* PhysfsIo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PhysfsIo.cpp; sourceTree = "<group>"; };
 		D943E58D2A24D56000D80361 /* PhysfsIo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhysfsIo.h; sourceTree = "<group>"; };
 		D943E58D2A24D56000D80361 /* PhysfsIo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhysfsIo.h; sourceTree = "<group>"; };
 		D9596F602CBAC93800BE58C1 /* SDL3.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = SDL3.xcframework; path = shared/Frameworks/SDL3.xcframework; sourceTree = "<group>"; };
 		D9596F602CBAC93800BE58C1 /* SDL3.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = SDL3.xcframework; path = shared/Frameworks/SDL3.xcframework; sourceTree = "<group>"; };
@@ -3386,8 +3389,9 @@
 			children = (
 			children = (
 				FA0B7CBF1A95902C000E1D17 /* sdl */,
 				FA0B7CBF1A95902C000E1D17 /* sdl */,
 				FA0B7CC21A95902C000E1D17 /* Touch.h */,
 				FA0B7CC21A95902C000E1D17 /* Touch.h */,
-				FA0B7CC31A95902C000E1D17 /* wrap_Touch.cpp */,
+				D93660F72D1C727C00C0EC4B /* Touch.cpp */,
 				FA0B7CC41A95902C000E1D17 /* wrap_Touch.h */,
 				FA0B7CC41A95902C000E1D17 /* wrap_Touch.h */,
+				FA0B7CC31A95902C000E1D17 /* wrap_Touch.cpp */,
 			);
 			);
 			path = touch;
 			path = touch;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
@@ -4797,6 +4801,7 @@
 				FA0B7DBC1A95902C000E1D17 /* Joystick.cpp in Sources */,
 				FA0B7DBC1A95902C000E1D17 /* Joystick.cpp in Sources */,
 				FA0B7DAF1A95902C000E1D17 /* wrap_CompressedImageData.cpp in Sources */,
 				FA0B7DAF1A95902C000E1D17 /* wrap_CompressedImageData.cpp in Sources */,
 				FA0B7AD51A958EA3000E1D17 /* unix.c in Sources */,
 				FA0B7AD51A958EA3000E1D17 /* unix.c in Sources */,
+				D93660F92D1C727C00C0EC4B /* Touch.cpp in Sources */,
 				FAB17BE71ABFAA9000F9BA27 /* lz4.c in Sources */,
 				FAB17BE71ABFAA9000F9BA27 /* lz4.c in Sources */,
 				FAE64A902071364800BC7981 /* physfs_unicode.c in Sources */,
 				FAE64A902071364800BC7981 /* physfs_unicode.c in Sources */,
 				FACA06B1293EE5CD001A2557 /* Sensor.cpp in Sources */,
 				FACA06B1293EE5CD001A2557 /* Sensor.cpp in Sources */,
@@ -5229,6 +5234,7 @@
 				FA0B7DC41A95902C000E1D17 /* wrap_JoystickModule.cpp in Sources */,
 				FA0B7DC41A95902C000E1D17 /* wrap_JoystickModule.cpp in Sources */,
 				FA0B7E6F1A95902C000E1D17 /* wrap_RopeJoint.cpp in Sources */,
 				FA0B7E6F1A95902C000E1D17 /* wrap_RopeJoint.cpp in Sources */,
 				FA24348721D401CB00B8918A /* attribute.cpp in Sources */,
 				FA24348721D401CB00B8918A /* attribute.cpp in Sources */,
+				D93660F82D1C727C00C0EC4B /* Touch.cpp in Sources */,
 				FA0B7AB51A958EA3000E1D17 /* ddsparse.cpp in Sources */,
 				FA0B7AB51A958EA3000E1D17 /* ddsparse.cpp in Sources */,
 				FACA02F41F5E396B0084B28F /* wrap_CompressedData.cpp in Sources */,
 				FACA02F41F5E396B0084B28F /* wrap_CompressedData.cpp in Sources */,
 				FAF140AC1E20934C00F898D2 /* Versions.cpp in Sources */,
 				FAF140AC1E20934C00F898D2 /* Versions.cpp in Sources */,

+ 41 - 35
src/modules/event/sdl/Event.cpp

@@ -317,45 +317,51 @@ Message *Event::convert(const SDL_Event &e)
 	case SDL_EVENT_FINGER_DOWN:
 	case SDL_EVENT_FINGER_DOWN:
 	case SDL_EVENT_FINGER_UP:
 	case SDL_EVENT_FINGER_UP:
 	case SDL_EVENT_FINGER_MOTION:
 	case SDL_EVENT_FINGER_MOTION:
-		// TODO: Expose APIs to enable different touch device types.
-		if (SDL_GetTouchDeviceType(e.tfinger.touchID) == SDL_TOUCH_DEVICE_DIRECT)
+		touchinfo.id = (int64)e.tfinger.fingerID;
+		touchinfo.x = e.tfinger.x;
+		touchinfo.y = e.tfinger.y;
+		touchinfo.dx = e.tfinger.dx;
+		touchinfo.dy = e.tfinger.dy;
+		touchinfo.pressure = e.tfinger.pressure;
+		touchinfo.deviceType = love::touch::sdl::Touch::getDeviceType(SDL_GetTouchDeviceType(e.tfinger.touchID));
+		touchinfo.mouse = e.tfinger.touchID == SDL_MOUSE_TOUCHID;
+
+		// SDL's coords are normalized to [0, 1], but we want screen coords for direct touches.
+		if (touchinfo.deviceType == love::touch::Touch::DEVICE_TOUCHSCREEN)
 		{
 		{
-			touchinfo.id = (int64) e.tfinger.fingerID;
-			touchinfo.x = e.tfinger.x;
-			touchinfo.y = e.tfinger.y;
-			touchinfo.dx = e.tfinger.dx;
-			touchinfo.dy = e.tfinger.dy;
-			touchinfo.pressure = e.tfinger.pressure;
-
-			// SDL's coords are normalized to [0, 1], but we want screen coords.
 			normalizedToDPICoords(&touchinfo.x, &touchinfo.y);
 			normalizedToDPICoords(&touchinfo.x, &touchinfo.y);
 			normalizedToDPICoords(&touchinfo.dx, &touchinfo.dy);
 			normalizedToDPICoords(&touchinfo.dx, &touchinfo.dy);
-
-			// We need to update the love.touch.sdl internal state from here.
-			touchmodule = (touch::sdl::Touch *) Module::getInstance("love.touch.sdl");
-			if (touchmodule)
-				touchmodule->onEvent(e.type, touchinfo);
-
-			// This is a bit hackish and we lose the higher 32 bits of the id on
-			// 32-bit systems, but SDL only ever gives id's that at most use as many
-			// bits as can fit in a pointer (for now.)
-			// We use lightuserdata instead of a lua_Number (double) because doubles
-			// can't represent all possible id values on 64-bit systems.
-			vargs.emplace_back((void *) (intptr_t) touchinfo.id);
-			vargs.emplace_back(touchinfo.x);
-			vargs.emplace_back(touchinfo.y);
-			vargs.emplace_back(touchinfo.dx);
-			vargs.emplace_back(touchinfo.dy);
-			vargs.emplace_back(touchinfo.pressure);
-
-			if (e.type == SDL_EVENT_FINGER_DOWN)
-				txt = "touchpressed";
-			else if (e.type == SDL_EVENT_FINGER_UP)
-				txt = "touchreleased";
-			else
-				txt = "touchmoved";
-			msg = new Message(txt, vargs);
 		}
 		}
+
+		// We need to update the love.touch.sdl internal state from here.
+		touchmodule = (touch::sdl::Touch *) Module::getInstance("love.touch.sdl");
+		if (touchmodule)
+			touchmodule->onEvent(e.type, touchinfo);
+
+		if (!love::touch::Touch::getConstant(touchinfo.deviceType, txt))
+			txt = "unknown";
+
+		// This is a bit hackish and we lose the higher 32 bits of the id on
+		// 32-bit systems, but SDL only ever gives id's that at most use as many
+		// bits as can fit in a pointer (for now.)
+		// We use lightuserdata instead of a lua_Number (double) because doubles
+		// can't represent all possible id values on 64-bit systems.
+		vargs.emplace_back((void *)(intptr_t)touchinfo.id);
+		vargs.emplace_back(touchinfo.x);
+		vargs.emplace_back(touchinfo.y);
+		vargs.emplace_back(touchinfo.dx);
+		vargs.emplace_back(touchinfo.dy);
+		vargs.emplace_back(touchinfo.pressure);
+		vargs.emplace_back(txt, strlen(txt));
+		vargs.emplace_back(touchinfo.mouse);
+
+		if (e.type == SDL_EVENT_FINGER_DOWN)
+			txt = "touchpressed";
+		else if (e.type == SDL_EVENT_FINGER_UP)
+			txt = "touchreleased";
+		else
+			txt = "touchmoved";
+		msg = new Message(txt, vargs);
 		break;
 		break;
 	case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
 	case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
 	case SDL_EVENT_JOYSTICK_BUTTON_UP:
 	case SDL_EVENT_JOYSTICK_BUTTON_UP:

+ 5 - 0
src/modules/love/boot.lua

@@ -220,6 +220,7 @@ function love.init()
 		highdpi = false,
 		highdpi = false,
 		renderers = nil, -- Moved to t.graphics.
 		renderers = nil, -- Moved to t.graphics.
 		excluderenderers = nil, -- Moved to t.graphics.
 		excluderenderers = nil, -- Moved to t.graphics.
+		trackpadtouch = false,
 	}
 	}
 
 
 	-- Console hack, part 1.
 	-- Console hack, part 1.
@@ -315,6 +316,10 @@ function love.init()
 		love._setHighDPIAllowed(c.highdpi)
 		love._setHighDPIAllowed(c.highdpi)
 	end
 	end
 
 
+	if love._setTrackpadTouch then
+		love._setTrackpadTouch(c.trackpadtouch)
+	end
+
 	if love._setAudioMixWithSystem then
 	if love._setAudioMixWithSystem then
 		if c.audio and c.audio.mixwithsystem ~= nil then
 		if c.audio and c.audio.mixwithsystem ~= nil then
 			love._setAudioMixWithSystem(c.audio.mixwithsystem)
 			love._setAudioMixWithSystem(c.audio.mixwithsystem)

+ 8 - 8
src/modules/love/callbacks.lua

@@ -52,14 +52,14 @@ function love.createhandlers()
 		wheelmoved = function (x,y,px,py,dir)
 		wheelmoved = function (x,y,px,py,dir)
 			if love.wheelmoved then return love.wheelmoved(x,y,px,py,dir) end
 			if love.wheelmoved then return love.wheelmoved(x,y,px,py,dir) end
 		end,
 		end,
-		touchpressed = function (id,x,y,dx,dy,p)
-			if love.touchpressed then return love.touchpressed(id,x,y,dx,dy,p) end
+		touchpressed = function (id,x,y,dx,dy,p,t,m)
+			if love.touchpressed then return love.touchpressed(id,x,y,dx,dy,p,t,m) end
 		end,
 		end,
-		touchreleased = function (id,x,y,dx,dy,p)
-			if love.touchreleased then return love.touchreleased(id,x,y,dx,dy,p) end
+		touchreleased = function (id,x,y,dx,dy,p,t,m)
+			if love.touchreleased then return love.touchreleased(id,x,y,dx,dy,p,t,m) end
 		end,
 		end,
-		touchmoved = function (id,x,y,dx,dy,p)
-			if love.touchmoved then return love.touchmoved(id,x,y,dx,dy,p) end
+		touchmoved = function (id,x,y,dx,dy,p,t,m)
+			if love.touchmoved then return love.touchmoved(id,x,y,dx,dy,p,t,m) end
 		end,
 		end,
 		joystickpressed = function (j,b)
 		joystickpressed = function (j,b)
 			if love.joystickpressed then return love.joystickpressed(j,b) end
 			if love.joystickpressed then return love.joystickpressed(j,b) end
@@ -163,13 +163,13 @@ function love.run()
 		-- Process events.
 		-- Process events.
 		if love.event then
 		if love.event then
 			love.event.pump()
 			love.event.pump()
-			for name, a,b,c,d,e,f in love.event.poll() do
+			for name, a,b,c,d,e,f,g,h in love.event.poll() do
 				if name == "quit" then
 				if name == "quit" then
 					if not love.quit or not love.quit() then
 					if not love.quit or not love.quit() then
 						return a or 0, b
 						return a or 0, b
 					end
 					end
 				end
 				end
-				love.handlers[name](a,b,c,d,e,f)
+				love.handlers[name](a,b,c,d,e,f,g,h)
 			end
 			end
 		end
 		end
 
 

+ 16 - 0
src/modules/love/love.cpp

@@ -78,6 +78,11 @@
 #	include "system/System.h"
 #	include "system/System.h"
 #endif
 #endif
 
 
+// For love::touch::setTrackpadTouch.
+#ifdef LOVE_ENABLE_TOUCH
+#	include "touch/Touch.h"
+#endif
+
 // Scripts.
 // Scripts.
 #include "scripts/nogame.lua.h"
 #include "scripts/nogame.lua.h"
 
 
@@ -439,6 +444,14 @@ static int w__setHighDPIAllowed(lua_State *L)
 	return 0;
 	return 0;
 }
 }
 
 
+static int w__setTrackpadTouch(lua_State *L)
+{
+#ifdef LOVE_ENABLE_TOUCH
+	love::touch::setTrackpadTouch((bool) lua_toboolean(L, 1));
+#endif
+	return 0;
+}
+
 static int w__setAudioMixWithSystem(lua_State *L)
 static int w__setAudioMixWithSystem(lua_State *L)
 {
 {
 	bool success = false;
 	bool success = false;
@@ -570,6 +583,9 @@ int luaopen_love(lua_State *L)
 	lua_pushcfunction(L, w__setHighDPIAllowed);
 	lua_pushcfunction(L, w__setHighDPIAllowed);
 	lua_setfield(L, -2, "_setHighDPIAllowed");
 	lua_setfield(L, -2, "_setHighDPIAllowed");
 
 
+	lua_pushcfunction(L, w__setTrackpadTouch);
+	lua_setfield(L, -2, "_setTrackpadTouch");
+
 	// Exposed here because we need to be able to call it before the audio
 	// Exposed here because we need to be able to call it before the audio
 	// module is initialized.
 	// module is initialized.
 	lua_pushcfunction(L, w__setAudioMixWithSystem);
 	lua_pushcfunction(L, w__setAudioMixWithSystem);

+ 46 - 0
src/modules/touch/Touch.cpp

@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2006-2024 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+#include "Touch.h"
+
+namespace love
+{
+namespace touch
+{
+
+// TODO: find a cleaner way to do this...
+// The touch backend (e.g. love.touch.sdl) is expected to implement this.
+void setTrackpadTouchImplementation(bool enable);
+
+void setTrackpadTouch(bool enable)
+{
+	setTrackpadTouchImplementation(enable);
+}
+
+STRINGMAP_CLASS_BEGIN(Touch, Touch::DeviceType, Touch::DEVICE_MAX_ENUM, deviceType)
+{
+	{ "touchscreen",      Touch::DEVICE_TOUCHSCREEN       },
+	{ "touchpad",         Touch::DEVICE_TOUCHPAD          },
+	{ "touchpadrelative", Touch::DEVICE_TOUCHPAD_RELATIVE },
+}
+STRINGMAP_CLASS_END(Touch, Touch::DeviceType, Touch::DEVICE_MAX_ENUM, deviceType)
+
+} // touch
+} // love

+ 19 - 4
src/modules/touch/Touch.h

@@ -25,6 +25,7 @@
 #include "common/int.h"
 #include "common/int.h"
 #include "common/Object.h"
 #include "common/Object.h"
 #include "common/Module.h"
 #include "common/Module.h"
+#include "common/StringMap.h"
 
 
 // C++
 // C++
 #include <vector>
 #include <vector>
@@ -35,18 +36,30 @@ namespace love
 namespace touch
 namespace touch
 {
 {
 
 
+void setTrackpadTouch(bool enable);
+
 class Touch : public Module
 class Touch : public Module
 {
 {
 public:
 public:
 
 
+	enum DeviceType
+	{
+		DEVICE_TOUCHSCREEN,
+		DEVICE_TOUCHPAD,
+		DEVICE_TOUCHPAD_RELATIVE,
+		DEVICE_MAX_ENUM
+	};
+
 	struct TouchInfo
 	struct TouchInfo
 	{
 	{
 		int64 id;  // Identifier. Only unique for the duration of the touch-press.
 		int64 id;  // Identifier. Only unique for the duration of the touch-press.
-		double x;  // Position in pixels along the x-axis.
-		double y;  // Position in pixels along the y-axis.
-		double dx; // Amount in pixels moved along the x-axis.
-		double dy; // Amount in pixels moved along the y-axis.
+		double x;  // Position in pixels (for touchscreens) or normalized [0, 1] position (for touchpads) along the x-axis.
+		double y;  // Position in pixels (for touchscreens) or normalized [0, 1] position (for touchpads) along the y-axis.
+		double dx; // Amount moved along the x-axis.
+		double dy; // Amount moved along the y-axis.
 		double pressure;
 		double pressure;
+		DeviceType deviceType;
+		bool mouse;
 	};
 	};
 
 
 	virtual ~Touch() {}
 	virtual ~Touch() {}
@@ -61,6 +74,8 @@ public:
 	 **/
 	 **/
 	virtual const TouchInfo &getTouch(int64 id) const = 0;
 	virtual const TouchInfo &getTouch(int64 id) const = 0;
 
 
+	STRINGMAP_CLASS_DECLARE(DeviceType);
+
 protected:
 protected:
 
 
 	Touch(const char *name)
 	Touch(const char *name)

+ 20 - 0
src/modules/touch/sdl/Touch.cpp

@@ -26,10 +26,19 @@
 // C++
 // C++
 #include <algorithm>
 #include <algorithm>
 
 
+#include <SDL3/SDL_hints.h>
+
 namespace love
 namespace love
 {
 {
 namespace touch
 namespace touch
 {
 {
+
+// See src/modules/touch/Touch.cpp.
+void setTrackpadTouchImplementation(bool enable)
+{
+	SDL_SetHint(SDL_HINT_TRACKPAD_IS_TOUCH_ONLY, enable ? "1" : "0");
+}
+
 namespace sdl
 namespace sdl
 {
 {
 
 
@@ -84,6 +93,17 @@ void Touch::onEvent(Uint32 eventtype, const TouchInfo &info)
 	}
 	}
 }
 }
 
 
+Touch::DeviceType Touch::getDeviceType(SDL_TouchDeviceType sdltype)
+{
+	switch (sdltype)
+	{
+		case SDL_TOUCH_DEVICE_DIRECT: return DEVICE_TOUCHSCREEN;
+		case SDL_TOUCH_DEVICE_INDIRECT_ABSOLUTE: return DEVICE_TOUCHPAD;
+		case SDL_TOUCH_DEVICE_INDIRECT_RELATIVE: return DEVICE_TOUCHPAD_RELATIVE;
+		default: return DEVICE_TOUCHSCREEN;
+	}
+}
+
 } // sdl
 } // sdl
 } // touch
 } // touch
 } // love
 } // love

+ 2 - 0
src/modules/touch/sdl/Touch.h

@@ -51,6 +51,8 @@ public:
 	// love::event::sdl::Event::convert.
 	// love::event::sdl::Event::convert.
 	void onEvent(Uint32 eventtype, const TouchInfo &info);
 	void onEvent(Uint32 eventtype, const TouchInfo &info);
 
 
+	static DeviceType getDeviceType(SDL_TouchDeviceType sdltype);
+
 private:
 private:
 
 
 	// All current touches.
 	// All current touches.

+ 51 - 8
src/modules/touch/wrap_Touch.cpp

@@ -24,6 +24,7 @@
 #include "wrap_Touch.h"
 #include "wrap_Touch.h"
 
 
 #include "sdl/Touch.h"
 #include "sdl/Touch.h"
+#include "common/Optional.h"
 
 
 namespace love
 namespace love
 {
 {
@@ -42,19 +43,33 @@ int64 luax_checktouchid(lua_State *L, int idx)
 
 
 int w_getTouches(lua_State *L)
 int w_getTouches(lua_State *L)
 {
 {
+	Optional<Touch::DeviceType> typefilter;
+	if (!lua_isnoneornil(L, 1))
+	{
+		const char *typestr = luaL_checkstring(L, 1);
+		if (!Touch::getConstant(typestr, typefilter.value))
+			return luax_enumerror(L, "touch device type", Touch::getConstants(typefilter.value), typestr);
+		typefilter.hasValue = true;
+	}
+
 	const std::vector<Touch::TouchInfo> &touches = instance()->getTouches();
 	const std::vector<Touch::TouchInfo> &touches = instance()->getTouches();
 
 
 	lua_createtable(L, (int) touches.size(), 0);
 	lua_createtable(L, (int) touches.size(), 0);
 
 
-	for (size_t i = 0; i < touches.size(); i++)
+	int filteredindex = 1;
+	for (const Touch::TouchInfo &touch : touches)
 	{
 	{
-		// This is a bit hackish and we lose the higher 32 bits of the id on
-		// 32-bit systems, but SDL only ever gives id's that at most use as many
-		// bits as can fit in a pointer (for now.)
-		// We use lightuserdata instead of a lua_Number (double) because doubles
-		// can't represent all possible id values on 64-bit systems.
-		lua_pushlightuserdata(L, (void *) (intptr_t) touches[i].id);
-		lua_rawseti(L, -2, (int) i + 1);
+		if (!typefilter.hasValue || typefilter.value == touch.deviceType)
+		{
+			// This is a bit hackish and we lose the higher 32 bits of the id on
+			// 32-bit systems, but SDL only ever gives id's that at most use as many
+			// bits as can fit in a pointer (for now.)
+			// We use lightuserdata instead of a lua_Number (double) because doubles
+			// can't represent all possible id values on 64-bit systems.
+			lua_pushlightuserdata(L, (void *)(intptr_t)touch.id);
+			lua_rawseti(L, -2, filteredindex);
+			filteredindex++;
+		}
 	}
 	}
 
 
 	return 1;
 	return 1;
@@ -84,11 +99,39 @@ int w_getPressure(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
+int w_getDeviceType(lua_State *L)
+{
+	int64 id = luax_checktouchid(L, 1);
+
+	Touch::TouchInfo touch = {};
+	luax_catchexcept(L, [&]() { touch = instance()->getTouch(id); });
+
+	const char *typestr = nullptr;
+	if (!Touch::getConstant(touch.deviceType, typestr))
+		return luaL_error(L, "Unknown touch device type.");
+
+	lua_pushstring(L, typestr);
+	return 1;
+}
+
+int w_isMouse(lua_State *L)
+{
+	int64 id = luax_checktouchid(L, 1);
+
+	Touch::TouchInfo touch = {};
+	luax_catchexcept(L, [&]() { touch = instance()->getTouch(id); });
+
+	luax_pushboolean(L, touch.mouse);
+	return 1;
+}
+
 static const luaL_Reg functions[] =
 static const luaL_Reg functions[] =
 {
 {
 	{ "getTouches", w_getTouches },
 	{ "getTouches", w_getTouches },
 	{ "getPosition", w_getPosition },
 	{ "getPosition", w_getPosition },
 	{ "getPressure", w_getPressure },
 	{ "getPressure", w_getPressure },
+	{ "getDeviceType", w_getDeviceType },
+	{ "isMouse", w_isMouse },
 	{ 0, 0 }
 	{ 0, 0 }
 };
 };