# Multiple touchscreen

**URL:** <https://discourse.libcinder.org/t/multiple-touchscreen/293>\
**Category:** Using Cinder\
**Created:** [September 30, 2016, 1:22am UTC](https://discourse.libcinder.org/t/multiple-touchscreen/293 "2016-09-30T01:22:40Z")\
**Posts on this page:** 17\
**Page:** 1

<div class="post-metadata">

**Author:** ![colin](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/colin/32/604_2.png) [@colin](https://discourse.libcinder.org/u/colin)\
**Post date:** [September 30, 2016, 1:22am UTC](https://discourse.libcinder.org/t/multiple-touchscreen/293/1 "2016-09-30T01:22:41Z")

</div>

Hello,

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

[CY-TE75LCC](http://www.samsung.com/uk/business/business-products/smart-signage/accessories/CY-TE75LCC/EN)

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

---

<div class="post-metadata">

**Author:** ![paul.houx](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/paul.houx/32/31_2.png) [@paul.houx](https://discourse.libcinder.org/u/paul.houx)\
**Post date:** [September 30, 2016, 1:27am UTC](https://discourse.libcinder.org/t/multiple-touchscreen/293/2 "2016-09-30T01:27:02Z")

</div>

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](http://forums.windowscentral.com/windows-10/377482-multiple-touch-screen-monitors.html)

I would make sure before buying the frames, though.

---

<div class="post-metadata">

**Author:** ![colin](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/colin/32/604_2.png) [@colin](https://discourse.libcinder.org/u/colin)\
**Post date:** [September 30, 2016, 1:52am UTC](https://discourse.libcinder.org/t/multiple-touchscreen/293/3 "2016-09-30T01:52:23Z")

</div>

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](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 !

---

<div class="post-metadata">

**Author:** ![colin](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/colin/32/604_2.png) [@colin](https://discourse.libcinder.org/u/colin)\
**Post date:** [October 4, 2016, 5:12pm UTC](https://discourse.libcinder.org/t/multiple-touchscreen/293/4 "2016-10-04T17:12:19Z")

</div>

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 !

---

<div class="post-metadata">

**Author:** ![paul.houx](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/paul.houx/32/31_2.png) [@paul.houx](https://discourse.libcinder.org/u/paul.houx)\
**Post date:** [October 4, 2016, 10:05pm UTC](https://discourse.libcinder.org/t/multiple-touchscreen/293/5 "2016-10-04T22:05:28Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![rogiermars](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/rogiermars/32/35_2.png) [@rogiermars](https://discourse.libcinder.org/u/rogiermars)\
**Post date:** [October 6, 2016, 11:56am UTC](https://discourse.libcinder.org/t/multiple-touchscreen/293/6 "2016-10-06T11:56:56Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![colin](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/colin/32/604_2.png) [@colin](https://discourse.libcinder.org/u/colin)\
**Post date:** [October 23, 2016, 5:16pm UTC](https://discourse.libcinder.org/t/multiple-touchscreen/293/7 "2016-10-23T17:16:29Z")

</div>

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](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](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](https://stackoverflow.com/questions/28841139/how-to-get-coordinates-of-touchscreen-rawdata-using-linux)

---

<div class="post-metadata">

**Author:** ![nay](https://avatars.discourse-cdn.com/v4/letter/n/8e7dd6/32.png) [@nay](https://discourse.libcinder.org/u/nay)\
**Post date:** [March 9, 2018, 12:47am UTC](https://discourse.libcinder.org/t/multiple-touchscreen/293/8 "2018-03-09T00:47:01Z")

</div>

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](https://github.com/TouchScript/TouchScript) 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.

---

<div class="post-metadata">

**Author:** ![lithium](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/lithium/32/38_2.png) [@lithium](https://discourse.libcinder.org/u/lithium)\
**Post date:** [March 9, 2018, 1:05am UTC](https://discourse.libcinder.org/t/multiple-touchscreen/293/9 "2018-03-09T01:05:44Z")

</div>

@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

---

<div class="post-metadata">

**Author:** ![nay](https://avatars.discourse-cdn.com/v4/letter/n/8e7dd6/32.png) [@nay](https://discourse.libcinder.org/u/nay)\
**Post date:** [March 9, 2018, 1:59am UTC](https://discourse.libcinder.org/t/multiple-touchscreen/293/10 "2018-03-09T01:59:17Z")

</div>

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:

> <https://github.com/cinder/Cinder/blob/master/src/cinder/app/msw/AppImplMsw.cpp#L980-L981>

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

> <https://github.com/TouchScript/TouchScript/blob/master/External/WindowsTouch/WindowsTouch.cpp#L64-L73>

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.

---

<div class="post-metadata">

**Author:** ![lithium](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/lithium/32/38_2.png) [@lithium](https://discourse.libcinder.org/u/lithium)\
**Post date:** [March 9, 2018, 2:33am UTC](https://discourse.libcinder.org/t/multiple-touchscreen/293/11 "2018-03-09T02:33:39Z")

</div>

Before you go altering cinder you might be able to [Hook](https://msdn.microsoft.com/en-us/library/windows/desktop/dd373640(v=vs.85).aspx) the window events to see what’s coming in.

---

<div class="post-metadata">

**Author:** ![nay](https://avatars.discourse-cdn.com/v4/letter/n/8e7dd6/32.png) [@nay](https://discourse.libcinder.org/u/nay)\
**Post date:** [March 9, 2018, 2:56am UTC](https://discourse.libcinder.org/t/multiple-touchscreen/293/12 "2018-03-09T02:56:44Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![nay](https://avatars.discourse-cdn.com/v4/letter/n/8e7dd6/32.png) [@nay](https://discourse.libcinder.org/u/nay)\
**Post date:** [March 9, 2018, 4:57am UTC](https://discourse.libcinder.org/t/multiple-touchscreen/293/13 "2018-03-09T04:57:10Z")

</div>

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 🙂 but here is an edit of the multi touch sample that prints touch data to console if anyone is keen:

```auto

/*
 * 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 )

```

---

<div class="post-metadata">

**Author:** ![benjaminbojko](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/benjaminbojko/32/591_2.png) [@benjaminbojko](https://discourse.libcinder.org/u/benjaminbojko)\
**Post date:** [March 9, 2018, 6:29pm UTC](https://discourse.libcinder.org/t/multiple-touchscreen/293/14 "2018-03-09T18:29:52Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![nay](https://avatars.discourse-cdn.com/v4/letter/n/8e7dd6/32.png) [@nay](https://discourse.libcinder.org/u/nay)\
**Post date:** [March 12, 2018, 6:48am UTC](https://discourse.libcinder.org/t/multiple-touchscreen/293/15 "2018-03-12T06:48:59Z")

</div>

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:**

```auto
#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:**

```auto
/*

// 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);
}

```

---

<div class="post-metadata">

**Author:** ![nay](https://avatars.discourse-cdn.com/v4/letter/n/8e7dd6/32.png) [@nay](https://discourse.libcinder.org/u/nay)\
**Post date:** [March 13, 2018, 12:54am UTC](https://discourse.libcinder.org/t/multiple-touchscreen/293/16 "2018-03-13T00:54:32Z")

</div>

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

**TouchWin8.h:**

```auto
// 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:**

```auto
#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;
	}

}

```

---

<div class="post-metadata">

**Author:** ![benjaminbojko](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.libcinder.org/benjaminbojko/32/591_2.png) [@benjaminbojko](https://discourse.libcinder.org/u/benjaminbojko)\
**Post date:** [March 13, 2018, 1:41pm UTC](https://discourse.libcinder.org/t/multiple-touchscreen/293/17 "2018-03-13T13:41:06Z")

</div>

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