Multiple touchscreen

Hello,

I want to use multiple touch screen overlay with one PC on windows 10 and libcinder like

CY-TE75LCC

Is it possible ?

Does Windows 10 works with 4 touchscreens natively without drivers via HID ?
Is it compatible with libcinder 9.0 ?

Can you share your experience please ?

Thanks

Cheers

Colin

Hard to say. Much depends on the touchscreen drivers and whether or not you can connect more than one at the same time. If you can, are you able to distinguish between them and do the coordinate conversion yourself? So far, I’ve only seen projects with a single touch frame (sometimes covering more than one display).

-Paul

Edit: according to this forum, you can indeed connect multiple touchscreens:
http://forums.windowscentral.com/windows-10/377482-multiple-touch-screen-monitors.html

I would make sure before buying the frames, though.

On windows 8 it was possible with tablet PC settings, I think :
https://packard-bell-scandic.custhelp.com/app/answers/detail/a_id/29398/~/setting-up-multiple-touchscreen-displays-in-windows-8

In tablet PC settings dialog box it says configure the stylus and touch screens

touch screens is plural !

Hello
I’m testing 2 touchscreens with windows 10 and multitouchbasicApp.
It’s easy to configure touchscreens in windows 10 just go to Tablet PC settings.
I have one window under 2 screens.
The sample multitouchbasicApp draw well events of the touch events of the 2 screens.
At the moment, It does not work if there are touches at the same time in the 2 touchscreens and one window as if there was a focus !

I’m looking for the solution and I’ll keep you posted !

This is such a specific issue (albeit a very interesting one) that I am afraid you’ll have to do some pioneering yourself, Colin! Would love to learn more about your findings.

I do know that the ir-overlays from zaagtech and pq-labs has such a function that you can basicly add as many touchoverlays to one machine as needed.

Make sure if you are going to choose for such a setup to contact zaagtech or pq-labs first and ask them directly if they support these kinds of setups.

Hello,
I think is not possible under windows.
https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/9c11871d-945c-4f7b-bdaa-a1b1452959b8/multitouch-and-multiscreen?forum=tabletandtouch

Hook global event work only with mouse and keyboard. I think it’s not possible with touch event.

Multiple touchscreen work well with one window per touchscreen see below :
https://bitbucket.org/colinbouvry/multiwindowsmultitouchscreen

I have other solution with linux and global event hook :
https://stackoverflow.com/questions/28841139/how-to-get-coordinates-of-touchscreen-rawdata-using-linux

hi @colin and everyone, just wondering if anyone has made any progress on this old issue? I have 2 touch screens connected to one windows 10 machine with a single window cinder app that spans both screens. I get accurate touches on each but only one screen at a time.

I have also tried finding a working solution that forwards MSW touch events to TUIO but no luck yet.

A colleague is working on the same hardware with unity and it works for him when setting it to use the windows 8 instead windows 7 api in the touch script package he is using. So maybe the api that cinder is using for touch can be updated from win 7 to 8 and this will start working? not sure where best to look for a solution so thought I’d check if anyone else has tackled this.

cheers
nay.

@nay I had to do this once and ended up dropping into the HID system and reading raw USB data to get it going. I have no idea if this is the best way to do it because it was the first thing I tried, but it worked. Is your hardware portable? If you want to bring it to snepo i can have a look at it for you

thanks @lithium
sadly we are dealing with 2 x 55" monitors but would definitely be up for having you over at the studio in chippendale if up for it? will ping you off this thread if it comes to that

however, I did take a look at the cinder touch event messages versus the touch script windows 8 stuff and it is pretty different. the touch script win 7 api and cinder only seem to use the WM_TOUCH message:


where as the touch script win 8 api uses a bunch more message types:

I’d be keen to start by compiling a version of cinder that prints these messages to console to see if receives them. If so, then it should be possible to tie them into the touch event handling (Although I have no experience with this low level windows stuff!). Looks like I might need to initialise things a bit differently to be able to receive them.

that raw USB data option sounds like a great fall back - would love to know more about that approach too.

Before you go altering cinder you might be able to Hook the window events to see what’s coming in.

ah thanks for the tip, will look into that first.

so no luck with the hook (I think colin had same issue in this thread) but I am able to get all touch events using code from TouchScript. the code breaks cinder’s in built touch functionality although I would be replacing it anyway. I can get away with setting this up on top of cinder although I think it’s def a good candidate for the core in future. I’m going to end the week on a high and look at refining this next week :slight_smile: but here is an edit of the multi touch sample that prints touch data to console if anyone is keen:


/*
 * Thanks to code from TouchScript:
 * https://github.com/TouchScript/TouchScript/tree/master/External/WindowsTouch
 */

#include "cinder/app/App.h"
#include "cinder/app/RendererGl.h"
#include "cinder/gl/gl.h"
#include "cinder/System.h"
#include "cinder/Rand.h"
#include "cinder/Log.h"

#include <vector>
#include <map>
#include <map>
#include <list>

using namespace ci;
using namespace ci::app;
using namespace std;

#include <Windows.h>

// <Windows 8 touch API>

#define WM_POINTERENTER				0x0249
#define WM_POINTERLEAVE				0x024A
#define WM_POINTERUPDATE			0x0245
#define WM_POINTERDOWN				0x0246
#define WM_POINTERUP				0x0247
#define WM_POINTERCAPTURECHANGED    0x024C
#define POINTER_CANCELLED			0x1000

#define GET_POINTERID_WPARAM(wParam)	(LOWORD(wParam))

typedef enum {
	PT_POINTER = 0x00000001,
	PT_TOUCH = 0x00000002,
	PT_PEN = 0x00000003,
	PT_MOUSE = 0x00000004,
	PT_TOUCHPAD = 0x00000005
} POINTER_INPUT_TYPE;

typedef enum {
	POINTER_FLAG_NONE = 0x00000000,
	POINTER_FLAG_NEW = 0x00000001,
	POINTER_FLAG_INRANGE = 0x00000002,
	POINTER_FLAG_INCONTACT = 0x00000004,
	POINTER_FLAG_FIRSTBUTTON = 0x00000010,
	POINTER_FLAG_SECONDBUTTON = 0x00000020,
	POINTER_FLAG_THIRDBUTTON = 0x00000040,
	POINTER_FLAG_FOURTHBUTTON = 0x00000080,
	POINTER_FLAG_FIFTHBUTTON = 0x00000100,
	POINTER_FLAG_PRIMARY = 0x00002000,
	POINTER_FLAG_CONFIDENCE = 0x00004000,
	POINTER_FLAG_CANCELED = 0x00008000,
	POINTER_FLAG_DOWN = 0x00010000,
	POINTER_FLAG_UPDATE = 0x00020000,
	POINTER_FLAG_UP = 0x00040000,
	POINTER_FLAG_WHEEL = 0x00080000,
	POINTER_FLAG_HWHEEL = 0x00100000,
	POINTER_FLAG_CAPTURECHANGED = 0x00200000,
	POINTER_FLAG_HASTRANSFORM = 0x00400000
} POINTER_FLAGS;

typedef enum {
	POINTER_CHANGE_NONE,
	POINTER_CHANGE_FIRSTBUTTON_DOWN,
	POINTER_CHANGE_FIRSTBUTTON_UP,
	POINTER_CHANGE_SECONDBUTTON_DOWN,
	POINTER_CHANGE_SECONDBUTTON_UP,
	POINTER_CHANGE_THIRDBUTTON_DOWN,
	POINTER_CHANGE_THIRDBUTTON_UP,
	POINTER_CHANGE_FOURTHBUTTON_DOWN,
	POINTER_CHANGE_FOURTHBUTTON_UP,
	POINTER_CHANGE_FIFTHBUTTON_DOWN,
	POINTER_CHANGE_FIFTHBUTTON_UP,
} POINTER_BUTTON_CHANGE_TYPE;

typedef enum {
	TOUCH_FLAG_NONE = 0x00000000
} TOUCH_FLAGS;

typedef enum {
	TOUCH_MASK_NONE = 0x00000000,
	TOUCH_MASK_CONTACTAREA = 0x00000001,
	TOUCH_MASK_ORIENTATION = 0x00000002,
	TOUCH_MASK_PRESSURE = 0x00000004
} TOUCH_MASK;

typedef enum {
	PEN_FLAG_NONE = 0x00000000,
	PEN_FLAG_BARREL = 0x00000001,
	PEN_FLAG_INVERTED = 0x00000002,
	PEN_FLAG_ERASER = 0x00000004
} PEN_FLAGS;

typedef enum {
	PEN_MASK_NONE = 0x00000000,
	PEN_MASK_PRESSURE = 0x00000001,
	PEN_MASK_ROTATION = 0x00000002,
	PEN_MASK_TILT_X = 0x00000004,
	PEN_MASK_TILT_Y = 0x00000008
} PEN_MASK;

typedef struct {
	POINTER_INPUT_TYPE		pointerType;
	UINT32					pointerId;
	UINT32					frameId;
	POINTER_FLAGS			pointerFlags;
	HANDLE					sourceDevice;
	HWND					hwndTarget;
	POINT					ptPixelLocation;
	POINT					ptHimetricLocation;
	POINT					ptPixelLocationRaw;
	POINT					ptHimetricLocationRaw;
	DWORD					dwTime;
	UINT32					historyCount;
	INT32					InputData;
	DWORD					dwKeyStates;
	UINT64					PerformanceCount;
	POINTER_BUTTON_CHANGE_TYPE ButtonChangeType;
} POINTER_INFO;

typedef struct {
	POINTER_INFO			pointerInfo;
	TOUCH_FLAGS				touchFlags;
	TOUCH_MASK				touchMask;
	RECT					rcContact;
	RECT					rcContactRaw;
	UINT32					orientation;
	UINT32					pressure;
} POINTER_TOUCH_INFO;

typedef struct {
	POINTER_INFO			pointerInfo;
	PEN_FLAGS				penFlags;
	PEN_MASK				penMask;
	UINT32					pressure;
	UINT32					rotation;
	INT32					tiltX;
	INT32					tiltY;
} POINTER_PEN_INFO;

typedef BOOL(WINAPI *GET_POINTER_INFO)(UINT32 pointerId, POINTER_INFO *pointerInfo);
typedef BOOL(WINAPI *GET_POINTER_TOUCH_INFO)(UINT32 pointerId, POINTER_TOUCH_INFO *pointerInfo);
typedef BOOL(WINAPI *GET_POINTER_PEN_INFO)(UINT32 pointerId, POINTER_PEN_INFO *pointerInfo);

GET_POINTER_INFO			GetPointerInfo;
GET_POINTER_TOUCH_INFO		GetPointerTouchInfo;
GET_POINTER_PEN_INFO		GetPointerPenInfo;

// </Windows 8 touch API>

struct PointerData
{
	POINTER_FLAGS			pointerFlags;
	UINT32					flags;
	UINT32					mask;
	POINTER_BUTTON_CHANGE_TYPE changedButtons;
	UINT32					rotation;
	UINT32					pressure;
	INT32					tiltX;
	INT32					tiltY;
};

HWND						_currentWindow;
LONG_PTR					_oldWindowProc;

struct TouchPoint {
	TouchPoint() {}
	TouchPoint( const vec2 &initialPt, const Color &color ) : mColor( color ), mTimeOfDeath( -1.0 ) 
	{
		mLine.push_back( initialPt ); 
	}
	
	void addPoint( const vec2 &pt ) { mLine.push_back( pt ); }
	
	void draw() const
	{
		if( mTimeOfDeath > 0 ) // are we dying? then fade out
			gl::color( ColorA( mColor, ( mTimeOfDeath - getElapsedSeconds() ) / 2.0f ) );
		else
			gl::color( mColor );

		gl::draw( mLine );
	}
	
	void startDying() { mTimeOfDeath = getElapsedSeconds() + 2.0f; } // two seconds til dead
	
	bool isDead() const { return getElapsedSeconds() > mTimeOfDeath; }
	
	PolyLine2f		mLine;
	Color			mColor;
	float			mTimeOfDeath;
};

class MultiTouchApp : public App {
 public:
	void	mouseDown( MouseEvent event ) override;
	void	mouseDrag( MouseEvent event ) override;

	void	touchesBegan( TouchEvent event ) override;
	void	touchesMoved( TouchEvent event ) override;
	void	touchesEnded( TouchEvent event ) override;

	void	setup() override;
	void	draw() override;

  private:
	map<uint32_t,TouchPoint>	mActivePoints;
	list<TouchPoint>			mDyingPoints;
};

void prepareSettings( MultiTouchApp::Settings *settings )
{
	// By default, multi-touch is disabled on desktop and enabled on mobile platforms.
	// You enable multi-touch from the SettingsFn that fires before the app is constructed.
	settings->setMultiTouchEnabled( true );

	// On mobile, if you disable multitouch then touch events will arrive via mouseDown(), mouseDrag(), etc.
//	settings->setMultiTouchEnabled( false );

	settings->setConsoleWindowEnabled(true);
}

void decodeWin8Touches(UINT msg, WPARAM wParam, LPARAM lParam)
{
	int pointerId = GET_POINTERID_WPARAM(wParam);

	POINTER_INFO pointerInfo;
	if (!GetPointerInfo(pointerId, &pointerInfo)) return;

	POINT p;
	p.x = pointerInfo.ptPixelLocation.x;
	p.y = pointerInfo.ptPixelLocation.y;
	ScreenToClient(_currentWindow, &p);

	vec2 position = vec2(((float)p.x - getWindowPos().x) * 1, getWindowHeight() - ((float)p.y - getWindowPos().y) * 1);
	PointerData data{};
	data.pointerFlags = pointerInfo.pointerFlags;
	data.changedButtons = pointerInfo.ButtonChangeType;

	if ((pointerInfo.pointerFlags & POINTER_FLAG_CANCELED) != 0
		|| msg == WM_POINTERCAPTURECHANGED) msg = POINTER_CANCELLED;

	switch (pointerInfo.pointerType)
	{
	case PT_MOUSE:
		break;
	case PT_TOUCH:
		POINTER_TOUCH_INFO touchInfo;
		GetPointerTouchInfo(pointerId, &touchInfo);
		data.flags = touchInfo.touchFlags;
		data.mask = touchInfo.touchMask;
		data.rotation = touchInfo.orientation;
		data.pressure = touchInfo.pressure;
		CI_LOG_I("touch " << pointerId << " : " << position);
		break;
	case PT_PEN:
		POINTER_PEN_INFO penInfo;
		GetPointerPenInfo(pointerId, &penInfo);
		data.flags = penInfo.penFlags;
		data.mask = penInfo.penMask;
		data.rotation = penInfo.rotation;
		data.pressure = penInfo.pressure;
		data.tiltX = penInfo.tiltX;
		data.tiltY = penInfo.tiltY;
		break;
	}

	//_delegate(pointerId, msg, pointerInfo.pointerType, position, data);//todo
}

LRESULT CALLBACK wndProc8(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{

	switch (msg)
	{
	case WM_TOUCH:
		//CI_LOG_I("Got win 7/8 touch event.");
		CloseTouchInputHandle((HTOUCHINPUT)lParam);
		break;
	case WM_POINTERENTER:
	case WM_POINTERLEAVE:
	case WM_POINTERDOWN:
	case WM_POINTERUP:
	case WM_POINTERUPDATE:
	case WM_POINTERCAPTURECHANGED:
		decodeWin8Touches(msg, wParam, lParam);
		//CI_LOG_I("Got win 8 touch event!");
		break;
	default:
		return CallWindowProc((WNDPROC)_oldWindowProc, hwnd, msg, wParam, lParam);
	}
	return 0;
}



void MultiTouchApp::setup()
{
	CI_LOG_I( "MT: " << System::hasMultiTouch() << " Max points: " << System::getMaxMultiTouchPoints() );

	_currentWindow = (HWND) getWindow()->getNative();

	HINSTANCE h = LoadLibrary(TEXT("user32.dll"));
	GetPointerInfo = (GET_POINTER_INFO)GetProcAddress(h, "GetPointerInfo");
	GetPointerTouchInfo = (GET_POINTER_TOUCH_INFO)GetProcAddress(h, "GetPointerTouchInfo");
	GetPointerPenInfo = (GET_POINTER_PEN_INFO)GetProcAddress(h, "GetPointerPenInfo");

	_oldWindowProc = SetWindowLongPtr(_currentWindow, GWLP_WNDPROC, (LONG_PTR)wndProc8);

}

void MultiTouchApp::touchesBegan( TouchEvent event )
{
	//CI_LOG_I( event );

	for( const auto &touch : event.getTouches() ) {
		Color newColor( CM_HSV, Rand::randFloat(), 1, 1 );
		mActivePoints.insert( make_pair( touch.getId(), TouchPoint( touch.getPos(), newColor ) ) );
	}
}

void MultiTouchApp::touchesMoved( TouchEvent event )
{
	//CI_LOG_I( event );
	for( const auto &touch : event.getTouches() ) {
		mActivePoints[touch.getId()].addPoint( touch.getPos() );
	}
}

void MultiTouchApp::touchesEnded( TouchEvent event )
{
	//CI_LOG_I( event );
	for( const auto &touch : event.getTouches() ) {
		mActivePoints[touch.getId()].startDying();
		mDyingPoints.push_back( mActivePoints[touch.getId()] );
		mActivePoints.erase( touch.getId() );
	}
}

void MultiTouchApp::mouseDown( MouseEvent event )
{
	//CI_LOG_I( "right: " << boolalpha << event.isRight() << " , control down " << event.isControlDown() << dec );
}

void MultiTouchApp::mouseDrag( MouseEvent event )
{
	//CI_LOG_I( "pos: " << event.getPos() );
}

void MultiTouchApp::draw()
{
	gl::enableAlphaBlending();
	gl::clear( Color( 0.1f, 0.1f, 0.1f ) );

	for( const auto &activePoint : mActivePoints ) {
		activePoint.second.draw();
	}

	for( auto dyingIt = mDyingPoints.begin(); dyingIt != mDyingPoints.end(); ) {
		dyingIt->draw();
		if( dyingIt->isDead() )
			dyingIt = mDyingPoints.erase( dyingIt );
		else
			++dyingIt;
	}
	
	// draw yellow circles at the active touch points
	gl::color( Color( 1, 1, 0 ) );
	for( const auto &touch : getActiveTouches() )
		gl::drawStrokedCircle( touch.getPos(), 20 );
}

CINDER_APP( MultiTouchApp, RendererGl, prepareSettings )

1 Like

Thanks for sharing your results! I’m super interested in this too and it’s a common problem we haven’t had a ton of success with at our studio either. One thing that we’ve heard working from AV integrators, but will obviously introduce some delay, is to have two low-end PCs receive your native touches and then relay them via TUIO to your main PC. It’s definitely a brute force way around the issue, but if you’re only looking for taps and simple gestures, it may help.

hi all, so I got this working with a version of the multi touch sample today, pasted below. I have it set to receive individual touch events similar to how the TUIO block events work. Integrating this into the core is beyond me at the moment although I imagine would be trivial for someone familiar with that code. I might get a chance to revisit after a couple deadlines.

MultiTouchBasicApp.cpp:

#include "cinder/app/App.h"
#include "cinder/app/RendererGl.h"
#include "cinder/gl/gl.h"
#include "cinder/System.h"
#include "cinder/Rand.h"
#include "cinder/Log.h"

#include <vector>
#include <map>
#include <map>
#include <list>

using namespace ci;
using namespace ci::app;
using namespace std;

#include "TouchWin8.h"

struct TouchPoint {
	TouchPoint() {}
	TouchPoint( const vec2 &initialPt, const Color &color ) : mColor( color ), mTimeOfDeath( -1.0 ) 
	{
		mLine.push_back( initialPt ); 
	}
	
	void addPoint( const vec2 &pt ) { mLine.push_back( pt ); }
	
	void draw() const
	{
		if( mTimeOfDeath > 0 ) // are we dying? then fade out
			gl::color( ColorA( mColor, ( mTimeOfDeath - getElapsedSeconds() ) / 2.0f ) );
		else
			gl::color( mColor );

		gl::draw( mLine );
	}
	
	void startDying() { mTimeOfDeath = getElapsedSeconds() + 2.0f; } // two seconds til dead
	
	bool isDead() const { return getElapsedSeconds() > mTimeOfDeath; }
	
	PolyLine2f		mLine;
	Color			mColor;
	float			mTimeOfDeath;
};

class MultiTouchApp : public App {
public:

	void	touchAdded(vec2 pos, int id);
	void	touchUpdated(vec2 pos, int id);
	void	touchRemoved(vec2 pos, int id);

	void	setup() override;
	void	draw() override;

  private:
	map<uint32_t,TouchPoint>	mActivePoints;
	list<TouchPoint>			mDyingPoints;
};

void prepareSettings( MultiTouchApp::Settings *settings )
{
	settings->setConsoleWindowEnabled(true);
}

void MultiTouchApp::setup()
{
	//set up touches from win 8 api
	initTouch();
	getSignalOnTouchAdded().connect(signals::slot(this, &MultiTouchApp::touchAdded));
	getSignalOnTouchUpdated().connect(signals::slot(this, &MultiTouchApp::touchUpdated));
	getSignalOnTouchRemoved().connect(signals::slot(this, &MultiTouchApp::touchRemoved));

}

void	MultiTouchApp::touchAdded(vec2 pos, int id)
{
	Color newColor(CM_HSV, Rand::randFloat(), 1, 1);
	mActivePoints.insert(make_pair(id, TouchPoint( pos, newColor)));
}

void	MultiTouchApp::touchUpdated(vec2 pos, int id)
{
	mActivePoints[id].addPoint(pos);
}

void	MultiTouchApp::touchRemoved(vec2 pos, int id)
{
	mActivePoints[id].startDying();
	mDyingPoints.push_back(mActivePoints[id]);
	mActivePoints.erase(id);
}


void MultiTouchApp::draw()
{
	gl::enableAlphaBlending();
	gl::clear( Color( 0.1f, 0.1f, 0.1f ) );

	for( const auto &activePoint : mActivePoints ) {
		activePoint.second.draw();
	}

	for( auto dyingIt = mDyingPoints.begin(); dyingIt != mDyingPoints.end(); ) {
		dyingIt->draw();
		if( dyingIt->isDead() )
			dyingIt = mDyingPoints.erase( dyingIt );
		else
			++dyingIt;
	}
	
	// draw yellow circles at the active touch points
	gl::color( Color( 1, 1, 0 ) );
	for( const auto &touch : getActiveTouches() )
		gl::drawStrokedCircle( touch.getPos(), 20 );
}

CINDER_APP( MultiTouchApp, RendererGl, prepareSettings )

TouchWin8.h:

/*

// Thanks to code from TouchScript:
// https://github.com/TouchScript/TouchScript/tree/master/External/WindowsTouch

*/

#pragma once

#include "cinder/app/App.h"
#include "cinder/Log.h"
#include "cinder/Signals.h"
#include <Windows.h>

ci::signals::Signal<void(ci::vec2, int)>  signalOnTouchAdded;
ci::signals::Signal<void(ci::vec2, int)>& getSignalOnTouchAdded() { return signalOnTouchAdded; }
ci::signals::Signal<void(ci::vec2, int)>  signalOnTouchUpdated;
ci::signals::Signal<void(ci::vec2, int)>& getSignalOnTouchUpdated() { return signalOnTouchUpdated; }
ci::signals::Signal<void(ci::vec2, int)>  signalOnTouchRemoved;
ci::signals::Signal<void(ci::vec2, int)>& getSignalOnTouchRemoved() { return signalOnTouchRemoved; }

// <Windows 8 touch API>

#define WM_POINTERENTER				0x0249
#define WM_POINTERLEAVE				0x024A
#define WM_POINTERUPDATE			0x0245
#define WM_POINTERDOWN				0x0246
#define WM_POINTERUP				0x0247
#define WM_POINTERCAPTURECHANGED    0x024C
#define POINTER_CANCELLED			0x1000

#define GET_POINTERID_WPARAM(wParam)	(LOWORD(wParam))

typedef enum {
	PT_POINTER = 0x00000001,
	PT_TOUCH = 0x00000002,
	PT_PEN = 0x00000003,
	PT_MOUSE = 0x00000004,
	PT_TOUCHPAD = 0x00000005
} POINTER_INPUT_TYPE;

typedef enum {
	POINTER_FLAG_NONE = 0x00000000,
	POINTER_FLAG_NEW = 0x00000001,
	POINTER_FLAG_INRANGE = 0x00000002,
	POINTER_FLAG_INCONTACT = 0x00000004,
	POINTER_FLAG_FIRSTBUTTON = 0x00000010,
	POINTER_FLAG_SECONDBUTTON = 0x00000020,
	POINTER_FLAG_THIRDBUTTON = 0x00000040,
	POINTER_FLAG_FOURTHBUTTON = 0x00000080,
	POINTER_FLAG_FIFTHBUTTON = 0x00000100,
	POINTER_FLAG_PRIMARY = 0x00002000,
	POINTER_FLAG_CONFIDENCE = 0x00004000,
	POINTER_FLAG_CANCELED = 0x00008000,
	POINTER_FLAG_DOWN = 0x00010000,
	POINTER_FLAG_UPDATE = 0x00020000,
	POINTER_FLAG_UP = 0x00040000,
	POINTER_FLAG_WHEEL = 0x00080000,
	POINTER_FLAG_HWHEEL = 0x00100000,
	POINTER_FLAG_CAPTURECHANGED = 0x00200000,
	POINTER_FLAG_HASTRANSFORM = 0x00400000
} POINTER_FLAGS;

typedef enum {
	POINTER_CHANGE_NONE,
	POINTER_CHANGE_FIRSTBUTTON_DOWN,
	POINTER_CHANGE_FIRSTBUTTON_UP,
	POINTER_CHANGE_SECONDBUTTON_DOWN,
	POINTER_CHANGE_SECONDBUTTON_UP,
	POINTER_CHANGE_THIRDBUTTON_DOWN,
	POINTER_CHANGE_THIRDBUTTON_UP,
	POINTER_CHANGE_FOURTHBUTTON_DOWN,
	POINTER_CHANGE_FOURTHBUTTON_UP,
	POINTER_CHANGE_FIFTHBUTTON_DOWN,
	POINTER_CHANGE_FIFTHBUTTON_UP,
} POINTER_BUTTON_CHANGE_TYPE;

typedef enum {
	TOUCH_FLAG_NONE = 0x00000000
} TOUCH_FLAGS;

typedef enum {
	TOUCH_MASK_NONE = 0x00000000,
	TOUCH_MASK_CONTACTAREA = 0x00000001,
	TOUCH_MASK_ORIENTATION = 0x00000002,
	TOUCH_MASK_PRESSURE = 0x00000004
} TOUCH_MASK;

typedef enum {
	PEN_FLAG_NONE = 0x00000000,
	PEN_FLAG_BARREL = 0x00000001,
	PEN_FLAG_INVERTED = 0x00000002,
	PEN_FLAG_ERASER = 0x00000004
} PEN_FLAGS;

typedef enum {
	PEN_MASK_NONE = 0x00000000,
	PEN_MASK_PRESSURE = 0x00000001,
	PEN_MASK_ROTATION = 0x00000002,
	PEN_MASK_TILT_X = 0x00000004,
	PEN_MASK_TILT_Y = 0x00000008
} PEN_MASK;

typedef struct {
	POINTER_INPUT_TYPE		pointerType;
	UINT32					pointerId;
	UINT32					frameId;
	POINTER_FLAGS			pointerFlags;
	HANDLE					sourceDevice;
	HWND					hwndTarget;
	POINT					ptPixelLocation;
	POINT					ptHimetricLocation;
	POINT					ptPixelLocationRaw;
	POINT					ptHimetricLocationRaw;
	DWORD					dwTime;
	UINT32					historyCount;
	INT32					InputData;
	DWORD					dwKeyStates;
	UINT64					PerformanceCount;
	POINTER_BUTTON_CHANGE_TYPE ButtonChangeType;
} POINTER_INFO;

typedef struct {
	POINTER_INFO			pointerInfo;
	TOUCH_FLAGS				touchFlags;
	TOUCH_MASK				touchMask;
	RECT					rcContact;
	RECT					rcContactRaw;
	UINT32					orientation;
	UINT32					pressure;
} POINTER_TOUCH_INFO;

typedef struct {
	POINTER_INFO			pointerInfo;
	PEN_FLAGS				penFlags;
	PEN_MASK				penMask;
	UINT32					pressure;
	UINT32					rotation;
	INT32					tiltX;
	INT32					tiltY;
} POINTER_PEN_INFO;

typedef BOOL(WINAPI *GET_POINTER_INFO)(UINT32 pointerId, POINTER_INFO *pointerInfo);
typedef BOOL(WINAPI *GET_POINTER_TOUCH_INFO)(UINT32 pointerId, POINTER_TOUCH_INFO *pointerInfo);
typedef BOOL(WINAPI *GET_POINTER_PEN_INFO)(UINT32 pointerId, POINTER_PEN_INFO *pointerInfo);

GET_POINTER_INFO			GetPointerInfo;
GET_POINTER_TOUCH_INFO		GetPointerTouchInfo;
GET_POINTER_PEN_INFO		GetPointerPenInfo;

// </Windows 8 touch API>

HWND						_currentWindow;
LONG_PTR					_oldWindowProc;


void decodeWin8Touches(UINT msg, WPARAM wParam, LPARAM lParam)
{
	int pointerId = GET_POINTERID_WPARAM(wParam);

	POINTER_INFO pointerInfo;
	if (!GetPointerInfo(pointerId, &pointerInfo)) return;

	POINT p;
	p.x = pointerInfo.ptPixelLocation.x;
	p.y = pointerInfo.ptPixelLocation.y;
	ScreenToClient(_currentWindow, &p);

	ci::vec2 position(p.x, p.y);

	switch (msg)
	{
	case WM_TOUCH:
		//CI_LOG_I("WM_TOUCH");
		//not used
		break;
	case WM_POINTERENTER:
		//CI_LOG_I("WM_POINTERENTER");
		//redundant as followed by WM_POINTERDOWN with same coords
		break;
	case WM_POINTERLEAVE:
		//CI_LOG_I("WM_POINTERLEAVE");
		//redundant as follows WM_POINTERUP with same coords
		break;
	case WM_POINTERDOWN:
		//CI_LOG_I("WM_POINTERDOWN");
		signalOnTouchAdded.emit(position, pointerId);
		break;
	case WM_POINTERUP:
		//CI_LOG_I("WM_POINTERUP");
		signalOnTouchRemoved.emit(position, pointerId);
		break;
	case WM_POINTERUPDATE:
		//CI_LOG_I("WM_POINTERUPDATE");
		signalOnTouchUpdated.emit(position, pointerId);
		break;
	case WM_POINTERCAPTURECHANGED:
		//CI_LOG_I("WM_POINTERCAPTURECHANGED");
		//not used
		break;
	}
	
}

LRESULT CALLBACK wndProc8(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{

	switch (msg)
	{
	case WM_TOUCH:
		CloseTouchInputHandle((HTOUCHINPUT)lParam);
		break;
	case WM_POINTERENTER:
	case WM_POINTERLEAVE:
	case WM_POINTERDOWN:
	case WM_POINTERUP:
	case WM_POINTERUPDATE:
	case WM_POINTERCAPTURECHANGED:
		decodeWin8Touches(msg, wParam, lParam);
		break;
	default:
		return CallWindowProc((WNDPROC)_oldWindowProc, hwnd, msg, wParam, lParam);
	}
	return 0;
}



void initTouch()
{
	_currentWindow = (HWND)getWindow()->getNative();

	HINSTANCE h = LoadLibrary(TEXT("user32.dll"));
	GetPointerInfo = (GET_POINTER_INFO)GetProcAddress(h, "GetPointerInfo");
	GetPointerTouchInfo = (GET_POINTER_TOUCH_INFO)GetProcAddress(h, "GetPointerTouchInfo");
	GetPointerPenInfo = (GET_POINTER_PEN_INFO)GetProcAddress(h, "GetPointerPenInfo");

	_oldWindowProc = SetWindowLongPtr(_currentWindow, GWLP_WNDPROC, (LONG_PTR)wndProc8);
}


4 Likes

and here is a less sloppy version of that TouchWin8 code split into header and implementation to avoid linker errors

TouchWin8.h:

// Thanks to code from TouchScript by Valentin Simonov / http://va.lent.in/
// https://github.com/TouchScript/TouchScript/tree/master/External/WindowsTouch

#pragma once

#include "cinder/app/App.h"
#include "cinder/Signals.h"
#include <Windows.h>

ci::signals::Signal< void(ci::vec2, int) > & getSignalOnTouchAdded();
ci::signals::Signal< void(ci::vec2, int) > & getSignalOnTouchUpdated();
ci::signals::Signal< void(ci::vec2, int) > & getSignalOnTouchRemoved();

// <Windows 8 touch API>

#define WM_POINTERENTER				0x0249
#define WM_POINTERLEAVE				0x024A
#define WM_POINTERUPDATE			0x0245
#define WM_POINTERDOWN				0x0246
#define WM_POINTERUP				0x0247
#define WM_POINTERCAPTURECHANGED    0x024C
#define POINTER_CANCELLED			0x1000

#define GET_POINTERID_WPARAM(wParam)	(LOWORD(wParam))

typedef enum {
	PT_POINTER = 0x00000001,
	PT_TOUCH = 0x00000002,
	PT_PEN = 0x00000003,
	PT_MOUSE = 0x00000004,
	PT_TOUCHPAD = 0x00000005
} POINTER_INPUT_TYPE;

typedef enum {
	POINTER_FLAG_NONE = 0x00000000,
	POINTER_FLAG_NEW = 0x00000001,
	POINTER_FLAG_INRANGE = 0x00000002,
	POINTER_FLAG_INCONTACT = 0x00000004,
	POINTER_FLAG_FIRSTBUTTON = 0x00000010,
	POINTER_FLAG_SECONDBUTTON = 0x00000020,
	POINTER_FLAG_THIRDBUTTON = 0x00000040,
	POINTER_FLAG_FOURTHBUTTON = 0x00000080,
	POINTER_FLAG_FIFTHBUTTON = 0x00000100,
	POINTER_FLAG_PRIMARY = 0x00002000,
	POINTER_FLAG_CONFIDENCE = 0x00004000,
	POINTER_FLAG_CANCELED = 0x00008000,
	POINTER_FLAG_DOWN = 0x00010000,
	POINTER_FLAG_UPDATE = 0x00020000,
	POINTER_FLAG_UP = 0x00040000,
	POINTER_FLAG_WHEEL = 0x00080000,
	POINTER_FLAG_HWHEEL = 0x00100000,
	POINTER_FLAG_CAPTURECHANGED = 0x00200000,
	POINTER_FLAG_HASTRANSFORM = 0x00400000
} POINTER_FLAGS;

typedef enum {
	POINTER_CHANGE_NONE,
	POINTER_CHANGE_FIRSTBUTTON_DOWN,
	POINTER_CHANGE_FIRSTBUTTON_UP,
	POINTER_CHANGE_SECONDBUTTON_DOWN,
	POINTER_CHANGE_SECONDBUTTON_UP,
	POINTER_CHANGE_THIRDBUTTON_DOWN,
	POINTER_CHANGE_THIRDBUTTON_UP,
	POINTER_CHANGE_FOURTHBUTTON_DOWN,
	POINTER_CHANGE_FOURTHBUTTON_UP,
	POINTER_CHANGE_FIFTHBUTTON_DOWN,
	POINTER_CHANGE_FIFTHBUTTON_UP,
} POINTER_BUTTON_CHANGE_TYPE;

typedef enum {
	TOUCH_FLAG_NONE = 0x00000000
} TOUCH_FLAGS;

typedef enum {
	TOUCH_MASK_NONE = 0x00000000,
	TOUCH_MASK_CONTACTAREA = 0x00000001,
	TOUCH_MASK_ORIENTATION = 0x00000002,
	TOUCH_MASK_PRESSURE = 0x00000004
} TOUCH_MASK;

typedef enum {
	PEN_FLAG_NONE = 0x00000000,
	PEN_FLAG_BARREL = 0x00000001,
	PEN_FLAG_INVERTED = 0x00000002,
	PEN_FLAG_ERASER = 0x00000004
} PEN_FLAGS;

typedef enum {
	PEN_MASK_NONE = 0x00000000,
	PEN_MASK_PRESSURE = 0x00000001,
	PEN_MASK_ROTATION = 0x00000002,
	PEN_MASK_TILT_X = 0x00000004,
	PEN_MASK_TILT_Y = 0x00000008
} PEN_MASK;

typedef struct {
	POINTER_INPUT_TYPE		pointerType;
	UINT32					pointerId;
	UINT32					frameId;
	POINTER_FLAGS			pointerFlags;
	HANDLE					sourceDevice;
	HWND					hwndTarget;
	POINT					ptPixelLocation;
	POINT					ptHimetricLocation;
	POINT					ptPixelLocationRaw;
	POINT					ptHimetricLocationRaw;
	DWORD					dwTime;
	UINT32					historyCount;
	INT32					InputData;
	DWORD					dwKeyStates;
	UINT64					PerformanceCount;
	POINTER_BUTTON_CHANGE_TYPE ButtonChangeType;
} POINTER_INFO;

typedef struct {
	POINTER_INFO			pointerInfo;
	TOUCH_FLAGS				touchFlags;
	TOUCH_MASK				touchMask;
	RECT					rcContact;
	RECT					rcContactRaw;
	UINT32					orientation;
	UINT32					pressure;
} POINTER_TOUCH_INFO;

typedef struct {
	POINTER_INFO			pointerInfo;
	PEN_FLAGS				penFlags;
	PEN_MASK				penMask;
	UINT32					pressure;
	UINT32					rotation;
	INT32					tiltX;
	INT32					tiltY;
} POINTER_PEN_INFO;

typedef BOOL(WINAPI *GET_POINTER_INFO)(UINT32 pointerId, POINTER_INFO *pointerInfo);
typedef BOOL(WINAPI *GET_POINTER_TOUCH_INFO)(UINT32 pointerId, POINTER_TOUCH_INFO *pointerInfo);
typedef BOOL(WINAPI *GET_POINTER_PEN_INFO)(UINT32 pointerId, POINTER_PEN_INFO *pointerInfo);

// </Windows 8 touch API>

#define EXPORT_API __declspec(dllexport) 

extern "C"
{
	EXPORT_API void __stdcall initTouch();
}

LRESULT CALLBACK wndProc8(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
void decodeWin8Touches(UINT msg, WPARAM wParam, LPARAM lParam);

TouchWin8.cpp:

#include "TouchWin8.h"
#include "cinder/Log.h"

ci::signals::Signal<void(ci::vec2, int)>  signalOnTouchAdded;
ci::signals::Signal<void(ci::vec2, int)>& getSignalOnTouchAdded() { return signalOnTouchAdded; }
ci::signals::Signal<void(ci::vec2, int)>  signalOnTouchUpdated;
ci::signals::Signal<void(ci::vec2, int)>& getSignalOnTouchUpdated() { return signalOnTouchUpdated; }
ci::signals::Signal<void(ci::vec2, int)>  signalOnTouchRemoved;
ci::signals::Signal<void(ci::vec2, int)>& getSignalOnTouchRemoved() { return signalOnTouchRemoved; }

GET_POINTER_INFO			GetPointerInfo;
GET_POINTER_TOUCH_INFO		GetPointerTouchInfo;
GET_POINTER_PEN_INFO		GetPointerPenInfo;

HWND						_currentWindow;
LONG_PTR					_oldWindowProc;

extern "C"
{
	void __stdcall initTouch()
	{
		_currentWindow = (HWND)ci::app::getWindow()->getNative();

		HINSTANCE h = LoadLibrary(TEXT("user32.dll"));
		GetPointerInfo = (GET_POINTER_INFO)GetProcAddress(h, "GetPointerInfo");
		GetPointerTouchInfo = (GET_POINTER_TOUCH_INFO)GetProcAddress(h, "GetPointerTouchInfo");
		GetPointerPenInfo = (GET_POINTER_PEN_INFO)GetProcAddress(h, "GetPointerPenInfo");

		_oldWindowProc = SetWindowLongPtr(_currentWindow, GWLP_WNDPROC, (LONG_PTR)wndProc8);
	}
}

LRESULT CALLBACK wndProc8(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{

	switch (msg)
	{
	case WM_TOUCH:
		CloseTouchInputHandle((HTOUCHINPUT)lParam);
		break;
	case WM_POINTERENTER:
	case WM_POINTERLEAVE:
	case WM_POINTERDOWN:
	case WM_POINTERUP:
	case WM_POINTERUPDATE:
	case WM_POINTERCAPTURECHANGED:
		decodeWin8Touches(msg, wParam, lParam);
		break;
	default:
		return CallWindowProc((WNDPROC)_oldWindowProc, hwnd, msg, wParam, lParam);
	}
	return 0;
}

void decodeWin8Touches(UINT msg, WPARAM wParam, LPARAM lParam)
{
	int pointerId = GET_POINTERID_WPARAM(wParam);

	POINTER_INFO pointerInfo;
	if (!GetPointerInfo(pointerId, &pointerInfo)) return;

	POINT p;
	p.x = pointerInfo.ptPixelLocation.x;
	p.y = pointerInfo.ptPixelLocation.y;
	ScreenToClient(_currentWindow, &p);

	ci::vec2 position(p.x, p.y);

	switch (msg)
	{
		case WM_TOUCH:
			//CI_LOG_I("WM_TOUCH");
			//not used
			break;
		case WM_POINTERENTER:
			//CI_LOG_I("WM_POINTERENTER");
			//redundant as followed by WM_POINTERDOWN with same coords
			break;
		case WM_POINTERLEAVE:
			//CI_LOG_I("WM_POINTERLEAVE");
			//redundant as follows WM_POINTERUP with same coords
			break;
		case WM_POINTERDOWN:
			//CI_LOG_I("WM_POINTERDOWN");
			signalOnTouchAdded.emit(position, pointerId);
			break;
		case WM_POINTERUP:
			//CI_LOG_I("WM_POINTERUP");
			signalOnTouchRemoved.emit(position, pointerId);
			break;
		case WM_POINTERUPDATE:
			//CI_LOG_I("WM_POINTERUPDATE");
			signalOnTouchUpdated.emit(position, pointerId);
			break;
		case WM_POINTERCAPTURECHANGED:
			//CI_LOG_I("WM_POINTERCAPTURECHANGED");
			//not used
			break;
	}

}

4 Likes

Thanks for sharing @nay! We’ll get a few touchscreens in within the next few weeks and will give this a whirl too.