question

GaelCharpentier-1812 avatar image
0 Votes"
GaelCharpentier-1812 asked GaelCharpentier-1812 commented

WM_TOUCH event not received anymore after screen disconnection in C# app

I have a C# application handling touch by hooking to the Windows API message loop and listening to WM_TOUCH event.

It work perfectly except when the screen is disconnected or switch off. When the screen is reconected, Windows OS still receive touch but my application don't receive touch event anymore.

Here the code used. On application start, I use RegisterTouchWindow to register for WM_TOUCH events. I use RegisterDeviceNotification to watch devices changes. On devices changes, IsTouchEnabled check the state of touch with GetSystemMetrics(SM_DIGITIZER). if touch is not available and it was registered previously with RegisterTouchWindow, I unregister it with UnregisterTouchWindow.

if it is available and has not been registered or has been unregistered, it register with RegisterTouchWindow. This is were it fail. RegisterTouchWindow return true but the WndProc will not received any WM_TOUCH event.

I tried to not Unregister touch when device is lost but it is the same result.

I checked the messages with Spy++. after the touch has been disconnected then recconected, there is no WM_TOUCH message appearing in spy++.

Here you can download the source code of a WPF test application to reproduce the probleme https://drive.google.com/file/d/1E-RdiHAH0mfIEFj1-hNaBzVCfhn1iaNp/view?usp=sharing

public static bool IsTouchEnabled() {
 var value = (SM_DIGITIZER_FLAG) GetSystemMetrics(SM_DIGITIZER);
 return value.HasFlag(SM_DIGITIZER_FLAG.NID_EXTERNAL_TOUCH) ||
  value.HasFlag(SM_DIGITIZER_FLAG.NID_INTEGRATED_TOUCH) ||
  value.HasFlag(SM_DIGITIZER_FLAG.NID_EXTERNAL_PEN) ||
  value.HasFlag(SM_DIGITIZER_FLAG.NID_INTEGRATED_PEN);
}

public static void RegisterUsbDeviceNotification(IntPtr windowHandle) {
 DevBroadcastDeviceinterface dbi = new DevBroadcastDeviceinterface {
  DeviceType = DbtDevtypDeviceinterface,
   Reserved = 0,
   ClassGuid = GuidDevinterfaceUSBDevice,
   Name = 0
 };

 dbi.Size = Marshal.SizeOf(dbi);
 IntPtr buffer = Marshal.AllocHGlobal(dbi.Size);
 Marshal.StructureToPtr(dbi, buffer, true);

 notificationHandle = RegisterDeviceNotification(windowHandle, buffer, 0);
}

protected void OnSourceInitialized(object o, EventArgs e) {
 var source = PresentationSource.FromVisual(win) as HwndSource;

 RegisterUsbDeviceNotification(source.Handle);

 RegisterTouchEvent(source.Handle);

 //setup windows hook here
 source.AddHook(WndProc);
}

private bool RegisterTouchEvent(IntPtr handle) {
 if (!IsTouchEnabled()) {
  logger.Warn("no Touch device available");
  if (_touchRegistered) {
   if (!UnregisterTouchWindow(handle))
    logger.Info("problem during touch unregistering");
   _touchRegistered = false;
   logger.Info("unregister touch event");
  }
  OnTouchNotAvailable();
  return false;
 }
 if (_touchRegistered)
  return true;

 if (!RegisterTouchWindow(handle, RegisterTouchFlags.TWF_WANTPALM)) {
  var err = Marshal.GetLastWin32Error();
  logger.Error("cant register touch window. error " + err);
  return false;
 } else
  logger.Info("Win Touch source initialysed");
 _touchRegistered = true;

 return true;
}

private IntPtr WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, ref Boolean handled) {
 if (msg == WmDevicechange) {

  WmDevicechangeParam p = (WmDevicechangeParam) wParam.ToInt32();
  logger.Info("Device changed -> " + p.ToString());
  var source = PresentationSource.FromVisual(win) as HwndSource;
  if (source != null)
   RegisterTouchEvent(source.Handle);
  return new IntPtr(1);
 }

 if (msg == WM_TOUCH) {
  handled = HandleTouch(wParam, lParam);
  return new IntPtr(1);
 }

 return IntPtr.Zero;
}

Here the log I get when I switch off the touch screen and switch it on again

Win Touch source initialysed
//Screen switched off ----------------------
Device changed -> DBT_DEVICEREMOVECOMPLETE
no Touch device available
unregister touch event
Device changed -> DBT_DEVNODES_CHANGED
no Touch device available
Device changed -> DBT_DEVNODES_CHANGED
no Touch device available
Device changed -> DBT_DEVNODES_CHANGED
no Touch device available
//Screen switched on ----------------------
Device changed -> DBT_DEVNODES_CHANGED
no Touch device available
Device changed -> DBT_DEVNODES_CHANGED
no Touch device available
Device changed -> DBT_DEVNODES_CHANGED
no Touch device available
Device changed -> DBT_DEVNODES_CHANGED
no Touch device available
Device changed -> DBT_DEVICEARRIVAL
no Touch device available
Device changed -> DBT_DEVNODES_CHANGED
Win Touch source initialysed
Device changed -> DBT_DEVNODES_CHANGED

Here all the PInvoke function and WinApi enum used

private static readonly Int32 WM_TOUCH = 0x0240;

public enum WmDevicechangeParam {
 DBT_CONFIGCHANGECANCELED = 0x0019,
  DBT_CONFIGCHANGED = 0x0018,
  DBT_CUSTOMEVENT = 0x8006,
  DBT_DEVICEARRIVAL = 0x8000,
  DBT_DEVICEQUERYREMOVE = 0x8001,
  DBT_DEVICEQUERYREMOVEFAILED = 0x8002,
  DBT_DEVICEREMOVECOMPLETE = 0x8004,
  DBT_DEVICEREMOVEPENDING = 0x8003,
  DBT_DEVICETYPESPECIFIC = 0x8005,
  DBT_DEVNODES_CHANGED = 0x0007,
  DBT_QUERYCHANGECONFIG = 0x0017,
  DBT_USERDEFINED = 0xFFFF,
}

// device is gone
public
const int WmDevicechange = 0x0219;

// device change event
private
const int DbtDevtypDeviceinterface = 5;

private
const int MAXTOUCHES_INDEX = 0x95;
private
const int SM_DIGITIZER = 94;
private
const uint WM_DISPLAYCHANGE = 0x007e;
private static readonly Guid GuidDevinterfaceUSBDevice = new Guid("A5DCBF10-6530-11D2-901F-00C04FB951ED");
private static readonly Int32 touchInputSize = Marshal.SizeOf(new TOUCHINPUT());

[DllImport("kernel32.dll")]
public static extern uint GetLastError();

[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern int GetSystemMetrics(int nIndex);

[DllImport("user32.dll", SetLastError = true)]
public static extern bool RegisterTouchWindow(IntPtr hwnd,
 [MarshalAs(UnmanagedType.U4)] RegisterTouchFlags flags);

[DllImport("user32.dll", SetLastError = true)]
static extern bool UnregisterTouchWindow(IntPtr hWnd);

[DllImport("user32")]
[
 return :MarshalAs(UnmanagedType.Bool)
]
private static extern void CloseTouchInputHandle(IntPtr lParam);

[DllImport("user32")]
[
 return :MarshalAs(UnmanagedType.Bool)
]
private static extern Boolean GetTouchInputInfo(IntPtr hTouchInput, Int32 cInputs, [In, Out] TOUCHINPUT[] pInputs, Int32 cbSize);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr RegisterDeviceNotification(IntPtr recipient, IntPtr notificationFilter, int flags);

// USB devices
[DllImport("user32.dll")]
private static extern bool UnregisterDeviceNotification(IntPtr handle);

[StructLayout(LayoutKind.Sequential)]
private struct DevBroadcastDeviceinterface {
 internal int Size;
 internal int DeviceType;
 internal int Reserved;
 internal Guid ClassGuid;
 internal short Name;
}

[StructLayout(LayoutKind.Sequential)]
private struct TOUCHINPUT {
 public Int32 x;
 public Int32 y;
 public IntPtr hSource;
 public Int32 dwID;
 public Int32 dwFlags;
 public Int32 dwMask;
 public Int32 dwTime;
 public IntPtr dwExtraInfo;
 public Int32 cxContact;
 public Int32 cyContact;
}

public enum DWFlags {
 TOUCHEVENTF_MOVE = 0x0001,

  TOUCHEVENTF_DOWN = 0x0002,

  TOUCHEVENTF_UP = 0x0004,
}

[Flags, Serializable]
public enum RegisterTouchFlags {
 TWF_NONE = 0x00000000,

  TWF_FINETOUCH = 0x00000001,

  TWF_WANTPALM = 0x00000002
}

[Flags]
public enum SM_DIGITIZER_FLAG {
 TABLET_CONFIG_NONE = 0x00000000, //    The input digitizer does not have touch capabilities.
  NID_INTEGRATED_TOUCH = 0x00000001, // An integrated touch digitizer is used for input.
  NID_EXTERNAL_TOUCH = 0x00000002, //   An external touch digitizer is used for input.
  NID_INTEGRATED_PEN = 0x00000004, //   An integrated pen digitizer is used for input.
  NID_EXTERNAL_PEN = 0x00000008, // An external pen digitizer is used for input.
  NID_MULTI_INPUT = 0x00000040, //  An input digitizer with support for multiple inputs is used for input.
  NID_READY = 0x00000080, //The input digitizer is ready for input. If this value is unset, it may mean that the tablet service is stopped, the digitizer is not supported, or digitizer drivers have not been installed.
}

private struct TouchMessage
{
    public int Count
    { get; private set; }

    public TOUCHINPUT[] inputs
    { get; private set; }

    public TouchMessage(TOUCHINPUT[] inputs, int count)
        : this()
    {
       this.inputs = inputs;
       this.Count = count;
    }
}

private static Int32 LoWord(Int32 number)
{
    return number & 0xffff;
}







dotnet-csharpwindows-apiwindows-wpf
· 4
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.


Did you check if RegisterTouchWindow always uses the correct hwnd?


0 Votes 0 ·

Yes, it always receive the same value. The same that worked on the first register.

0 Votes 0 ·

Sorry for more details.
What's device you are testing? such as desktop/laptop, OS version, touch screen,etc.
Why did you say Windows OS still receive touch although there is no WM_TOUCH message appearing in spy++?

0 Votes 0 ·

I am using windows 10. I reproduce the probleme with different external USB touch screen.

I said Windows OS still receive touch because other touch application still work. For example, in google map in Chrome I can still do the pinch to zoom gesture after disconecting and reconnecting the touch screen.

But when I checked Chrome messages with spy++, I saw that it listen VM_POINTERDOWN/UPDATE/UP instead of WM_TOUCH. Maybe I should use this instead of WM_TOUCH

0 Votes 0 ·

0 Answers