Browse Source

Merge bea707a6fd into 3068850405

pull/20366/merge
Krzysztof Krysiński 3 days ago
committed by GitHub
parent
commit
35ed034488
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 9
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  2. 326
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  3. 56
      src/Windows/Avalonia.Win32/WindowImpl.cs
  4. 532
      src/Windows/Avalonia.Win32/WintabImpl/WintabContext.cs
  5. 705
      src/Windows/Avalonia.Win32/WintabImpl/WintabData.cs
  6. 484
      src/Windows/Avalonia.Win32/WintabImpl/WintabFuncs.cs
  7. 512
      src/Windows/Avalonia.Win32/WintabImpl/WintabInfo.cs
  8. 81
      src/Windows/Avalonia.Win32/WintabImpl/WintabMemUtils.cs

9
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -1210,6 +1210,15 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll", SetLastError = true)]
public static extern bool IsMouseInPointerEnabled();
public const uint SIGNATURE_MASK = 0xFFFFFF00;
public const uint PEN_ID_SIGNATURE = 0xFF515700;
public static bool IsMessageFromPen()
{
IntPtr extra = GetMessageExtraInfo();
return (extra & SIGNATURE_MASK) == PEN_ID_SIGNATURE;
}
[DllImport("user32.dll", SetLastError = true)]
public static extern int EnableMouseInPointer(bool enable);

326
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Avalonia.Automation.Peers;
@ -10,6 +11,7 @@ using Avalonia.Threading;
using Avalonia.Win32.Automation;
using Avalonia.Win32.Automation.Interop;
using Avalonia.Win32.Input;
using Avalonia.Win32.WintabImpl;
using static Avalonia.Win32.Interop.UnmanagedMethods;
namespace Avalonia.Win32
@ -30,8 +32,55 @@ namespace Avalonia.Win32
RawInputEventArgs? e = null;
var shouldTakeFocus = false;
var message = (WindowsMessage)msg;
switch (message)
{
case(WindowsMessage)(int)EWintabEventMessage.WT_PROXIMITY:
case (WindowsMessage)(int)EWintabEventMessage.WT_PACKET:
{
// lParam is the HCTX (Wintab Context Handle)
// wParam is the serial number of the packet
if(_hCtx.HCtx == IntPtr.Zero)
break;
uint pktId = (uint)ToInt32(wParam);
if (pktId == 0)
{
break;
}
WintabPacket packet = _wnData.GetDataPacket(_hCtx.HCtx, pktId);
if (packet.pkContext != 0)
{
var raw = CreateRawPointerPoint(packet);
var eventType = GetEventType(packet);
var args = CreatePointerArgs(_penDevice, packet.pkTime, eventType, raw, GetInputModifiers(packet), packet.pkCursor);
FlushIntermediatePackets(pktId - _lastProcessedPacketSerial);
uint lastPktId = _lastProcessedPacketSerial;
args.IntermediatePoints =
new Lazy<IReadOnlyList<RawPointerPoint>?>(() => CreateIntermediatePoints(lastPktId, pktId));
e = args;
if (!_inkTestPerformed)
{
// Test if windows ink should be used instead
_hCtx.Close();
_wintabEnabled = false;
_nextPointerEventIsInkTest = true;
_inkTestPerformed = true;
break;
}
_lastProcessedPacketSerial = pktId;
}
_lastWintabTime = timestamp;
break;
}
case WindowsMessage.WM_ACTIVATE:
{
var wa = (WindowActivate)(ToInt32(wParam) & 0xffff);
@ -43,12 +92,31 @@ namespace Avalonia.Win32
{
Activated?.Invoke();
UpdateInputMethod(GetKeyboardLayout(0));
_inkTestPerformed = false; // Reset ink test flag on activation
if (_wintabEnabled)
{
WintabFuncs.WTEnable(_hCtx.HCtx, true);
WintabFuncs.WTOverlap(_hCtx.HCtx, true);
}
else
{
InitWintab();
}
break;
}
case WindowActivate.WA_INACTIVE:
{
Deactivated?.Invoke();
if (_wintabEnabled)
{
WintabFuncs.WTEnable(_hCtx.HCtx, false);
WintabFuncs.WTOverlap(_hCtx.HCtx, false);
}
break;
}
}
@ -161,6 +229,11 @@ namespace Avalonia.Win32
Imm32InputMethod.Current.ClearLanguageAndWindow();
}
if (_wintabEnabled)
{
_hCtx.Close();
}
// Cleanup render targets
(_glSurface as IDisposable)?.Dispose();
@ -284,6 +357,9 @@ namespace Avalonia.Win32
{
break;
}
if (Math.Abs((int)timestamp - (int)_lastWintabTime) < 50) break;
shouldTakeFocus = ShouldTakeFocusOnClick;
if (ShouldIgnoreTouchEmulatedMessage())
{
@ -345,59 +421,66 @@ namespace Avalonia.Win32
}
case WindowsMessage.WM_MOUSEMOVE:
{
if (_nextPointerEventIsInkTest && Math.Abs((int)timestamp - (int)_lastWintabTime) >= 10)
{
if (IsMouseInPointerEnabled)
{
break;
}
if (ShouldIgnoreTouchEmulatedMessage())
if (!IsMessageFromPen())
{
break;
InitWintab();
}
if (!_trackingMouse)
{
var tm = new TRACKMOUSEEVENT
{
cbSize = Marshal.SizeOf<TRACKMOUSEEVENT>(),
dwFlags = 2,
hwndTrack = _hwnd,
dwHoverTime = 0,
};
_nextPointerEventIsInkTest = false;
break;
}
TrackMouseEvent(ref tm);
}
if (IsMouseInPointerEnabled)
{
break;
}
var point = DipFromLParam(lParam);
if (Math.Abs((int)timestamp - (int)_lastWintabTime) < 50) break;
// Prepare points for the IntermediatePoints call.
var p = new POINT()
{
X = (int)(point.X * RenderScaling),
Y = (int)(point.Y * RenderScaling)
};
ClientToScreen(_hwnd, ref p);
var currPoint = new MOUSEMOVEPOINT()
{
x = p.X & 0xFFFF,
y = p.Y & 0xFFFF,
time = (int)timestamp
};
var prevPoint = _lastWmMousePoint;
_lastWmMousePoint = currPoint;
if (ShouldIgnoreTouchEmulatedMessage())
{
break;
e = new RawPointerEventArgs(
_mouseDevice,
timestamp,
Owner,
RawPointerEventType.Move,
point,
GetMouseModifiers(wParam))
}
if (!_trackingMouse)
{
var tm = new TRACKMOUSEEVENT
{
IntermediatePoints = new Lazy<IReadOnlyList<RawPointerPoint>?>(() => CreateIntermediatePoints(currPoint, prevPoint))
cbSize = Marshal.SizeOf<TRACKMOUSEEVENT>(),
dwFlags = 2,
hwndTrack = _hwnd,
dwHoverTime = 0,
};
break;
TrackMouseEvent(ref tm);
}
var point = DipFromLParam(lParam);
// Prepare points for the IntermediatePoints call.
var p = new POINT() { X = (int)(point.X * RenderScaling), Y = (int)(point.Y * RenderScaling) };
ClientToScreen(_hwnd, ref p);
var currPoint = new MOUSEMOVEPOINT() { x = p.X & 0xFFFF, y = p.Y & 0xFFFF, time = (int)timestamp };
var prevPoint = _lastWmMousePoint;
_lastWmMousePoint = currPoint;
e = new RawPointerEventArgs(
_mouseDevice,
timestamp,
Owner,
RawPointerEventType.Move,
point,
GetMouseModifiers(wParam))
{
IntermediatePoints =
new Lazy<IReadOnlyList<RawPointerPoint>?>(() =>
CreateIntermediatePoints(currPoint, prevPoint))
};
break;
}
case WindowsMessage.WM_MOUSEWHEEL:
@ -580,6 +663,8 @@ namespace Avalonia.Win32
var args = CreatePointerArgs(device, timestamp, eventType, point, modifiers, info.pointerId);
args.IntermediatePoints = CreateLazyIntermediatePoints(info);
e = args;
_nextPointerEventIsInkTest = false;
break;
}
case WindowsMessage.WM_POINTERDEVICEOUTOFRANGE:
@ -996,6 +1081,36 @@ namespace Avalonia.Win32
return DefWindowProc(hWnd, msg, wParam, lParam);
}
private RawInputModifiers GetInputModifiers(WintabPacket packet)
{
bool barrelDown = (packet.pkButtons & 0x002) != 0;
var modifiers = WindowsKeyboardDevice.Instance.Modifiers;
if (barrelDown)
{
modifiers |= RawInputModifiers.PenBarrelButton;
}
bool isEraser = packet.pkCursor == 2;
if(isEraser)
{
modifiers |= RawInputModifiers.PenEraser;
}
bool isInverted = (packet.pkStatus & (uint)EWintabPacketStatusValue.TPS_INVERT) != 0;
if (isInverted)
{
modifiers |= RawInputModifiers.PenInverted;
}
if(packet.pkNormalPressure > 0)
{
modifiers |= RawInputModifiers.LeftMouseButton;
}
return modifiers;
}
internal bool IsOurWindow(IntPtr hwnd)
{
if (hwnd == IntPtr.Zero)
@ -1149,6 +1264,61 @@ namespace Avalonia.Win32
}
private IReadOnlyList<RawPointerPoint>? CreateIntermediatePoints(uint lastPacket, uint currentPacketSerial)
{
if (lastPacket + 1 >= currentPacketSerial)
{
return null;
}
s_intermediatePointsPooledList.Clear();
s_intermediatePointsPooledList.Capacity = (int)(currentPacketSerial - lastPacket - 1);
for (uint pkSerial = lastPacket + 1; pkSerial < currentPacketSerial; pkSerial++)
{
if (s_lastWintabPackets.TryGetValue(pkSerial, out var packet))
{
s_intermediatePointsPooledList.Add(CreateRawPointerPoint(packet));
}
}
return s_intermediatePointsPooledList;
}
private void FlushIntermediatePackets(uint count)
{
if(count == 0)
return;
uint actualSize = 0;
var packets = _wnData.GetDataPackets(Math.Min(count, MaxWintabPacketHistorySize), true, ref actualSize);
foreach (var packet in packets)
{
s_lastWintabPackets[packet.pkSerialNumber] = packet;
}
if (s_lastWintabPackets.Count > MaxWintabPacketHistorySize)
{
var keysToRemove = s_lastWintabPackets.Keys
.OrderBy(k => k)
.Take(s_lastWintabPackets.Count - MaxWintabPacketHistorySize)
.ToList();
foreach (var key in keysToRemove)
{
s_lastWintabPackets.Remove(key);
}
}
}
public Point MapWintabToClientLocation(int pkX, int pkY)
{
int screenHeight = GetSystemMetrics(SystemMetric.SM_CYVIRTUALSCREEN);
int screenTop = GetSystemMetrics(SystemMetric.SM_YVIRTUALSCREEN);
int invertedY = screenHeight + screenTop - pkY;
return PointToClient(new Point(pkX, invertedY));
}
private RawPointerEventArgs CreatePointerArgs(IInputDevice device, ulong timestamp, RawPointerEventType eventType, RawPointerPoint point, RawInputModifiers modifiers, uint rawPointerId)
{
return device is TouchDevice
@ -1267,6 +1437,78 @@ namespace Avalonia.Win32
};
}
private RawPointerEventType GetEventType(WintabPacket packet)
{
bool isTipDown = packet.pkNormalPressure > 0;
bool isBarrelDown = (packet.pkButtons & (uint)EWintabPacketButtonCode.TBN_DOWN) != 0;
RawPointerEventType eventType;
if (isTipDown && !_wasTipDown)
{
eventType = RawPointerEventType.LeftButtonDown;
}
else if (!isTipDown && _wasTipDown)
{
eventType = RawPointerEventType.LeftButtonUp;
}
else if (isBarrelDown && !_wasBarrelDown)
{
eventType = RawPointerEventType.RightButtonDown;
}
else if (!isBarrelDown && _wasBarrelDown)
{
eventType = RawPointerEventType.RightButtonUp;
}
else
{
eventType = RawPointerEventType.Move;
}
_wasTipDown = isTipDown;
_wasBarrelDown = isBarrelDown;
return eventType;
}
private RawPointerPoint CreateRawPointerPoint(WintabPacket packet)
{
var point = MapWintabToClientLocation(packet.pkX, packet.pkY);
var (xTilt, yTilt) = ConvertWintabToStandardTilt(packet.pkOrientation.orAzimuth, packet.pkOrientation.orAltitude);
return new RawPointerPoint
{
Position = point,
Pressure = packet.pkNormalPressure / (float)_maxPressure,
XTilt = xTilt,
YTilt = yTilt,
Twist = packet.pkOrientation.orTwist
};
}
public (float xTilt, float yTilt) ConvertWintabToStandardTilt(double rawAzimuth, double rawAltitude)
{
float azimuthDeg = (float)rawAzimuth / 10f;
float altitudeDeg = (float)rawAltitude / 10f;
float azimuthRad = azimuthDeg * MathF.PI / 180f;
float tiltFromVertical = 90f - altitudeDeg;
float tiltX, tiltY;
tiltX = tiltFromVertical * MathF.Sin(azimuthRad);
tiltY = tiltFromVertical * MathF.Cos(azimuthRad);
tiltY = -tiltY;
tiltX = Math.Clamp(tiltX, -90f, 90f);
tiltY = Math.Clamp(tiltY, -90f, 90f);
return (tiltX, tiltY);
}
private static RawPointerEventType GetEventType(WindowsMessage message, POINTER_INFO info)
{
var isTouch = info.pointerType == PointerInputType.PT_TOUCH;

56
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -28,6 +28,7 @@ using Avalonia.Platform.Storage.FileIO;
using Avalonia.Threading;
using static Avalonia.Controls.Win32Properties;
using Avalonia.Logging;
using Avalonia.Win32.WintabImpl;
namespace Avalonia.Win32
{
@ -109,8 +110,22 @@ namespace Avalonia.Win32
private WindowTransparencyLevel _transparencyLevel;
private readonly WindowTransparencyLevel _defaultTransparencyLevel;
private WintabContext _hCtx;
private WintabData _wnData;
private bool _wasTipDown;
private bool _wasBarrelDown;
private bool _nextPointerEventIsInkTest;
private bool _inkTestPerformed;
private uint _lastProcessedPacketSerial;
private uint _wintabQueueSize;
private uint _lastWintabTime;
private double _maxPressure;
private bool _wintabEnabled;
private const int MaxPointerHistorySize = 512;
private const int MaxWintabPacketHistorySize = 1024;
private static readonly PooledList<RawPointerPoint> s_intermediatePointsPooledList = new();
private static readonly Dictionary<uint, WintabPacket> s_lastWintabPackets = new();
private static readonly List<InternalPoint> s_sortedPoints = new(64);
private static POINTER_TOUCH_INFO[]? s_historyTouchInfos;
private static POINTER_PEN_INFO[]? s_historyPenInfos;
@ -181,10 +196,51 @@ namespace Avalonia.Win32
_defaultTransparencyLevel = UseRedirectionBitmap ? WindowTransparencyLevel.None : WindowTransparencyLevel.Transparent;
_transparencyLevel = _defaultTransparencyLevel;
_hCtx = new WintabContext();
_wnData = new WintabData(_hCtx);
InitWintab();
lock (s_instances)
s_instances.Add(this);
}
private void InitWintab()
{
if (WintabInfo.IsWintabAvailable())
{
try
{
// Open system context, no need for digitizer
_hCtx = WintabContext.GetDefaultContext(EWTICategoryIndex.WTI_DEFSYSCTX);
_hCtx.Options |= (uint)ECTXOptionValues.CXO_SYSTEM | (uint)ECTXOptionValues.CXO_MESSAGES;
_hCtx.PktMode = 0; // Absolute mode
_hCtx.SysMode = false;
_wnData = new WintabData(_hCtx);
_hCtx.Open(_hwnd);
_wintabQueueSize = 128;
if (!_wnData.SetPacketQueueSize(_wintabQueueSize))
{
_wintabQueueSize = 32;
if (!_wnData.SetPacketQueueSize(_wintabQueueSize))
{
_wintabQueueSize = 8; // default
}
}
s_lastWintabPackets.Clear();
_maxPressure = WintabInfo.GetMaxPressure();
_wintabEnabled = true;
}
catch (Exception ex)
{
Logger.TryGet(LogEventLevel.Error, LogArea.Win32Platform)
?.Log(this, "Failed to initialize Wintab: {0}", ex);
_wintabEnabled = false;
}
}
}
internal IInputRoot Owner
=> _owner ?? throw new InvalidOperationException($"{nameof(SetInputRoot)} must have been called");

532
src/Windows/Avalonia.Win32/WintabImpl/WintabContext.cs

@ -0,0 +1,532 @@
///////////////////////////////////////////////////////////////////////////////
//
// PURPOSE
// Wintab context management for WintabDN
//
// COPYRIGHT
// Copyright (c) 2010-2020 Wacom Co., Ltd.
//
// The text and information contained in this file may be freely used,
// copied, or distributed without compensation or licensing restrictions.
//
///////////////////////////////////////////////////////////////////////////////
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Win32.WintabImpl
{
/// <summary>
/// Managed version of AXIS struct.
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct WintabAxis
{
/// <summary>
/// Specifies the minimum value of the data item in the tablet's na-tive coordinates.
/// </summary>
public Int32 axMin;
/// <summary>
/// Specifies the maximum value of the data item in the tablet's na-tive coordinates.
/// </summary>
public Int32 axMax;
/// <summary>
/// Indicates the units used in calculating the resolution for the data item.
/// </summary>
public UInt32 axUnits;
/// <summary>
/// Is a fixed-point number giving the number of data item incre-ments per physical unit.
/// </summary>
public FIX32 axResolution;
}
/// <summary>
/// Array of WintabAxis objects.
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct WintabAxisArray
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public WintabAxis[] array;
}
/// <summary>
/// Values to use when asking for X, Y or Z WintabAxis object.
/// </summary>
public enum EAxisDimension
{
AXIS_X = EWTIDevicesIndex.DVC_X,
AXIS_Y = EWTIDevicesIndex.DVC_Y,
AXIS_Z = EWTIDevicesIndex.DVC_Z
}
/// <summary>
/// Context option values.
/// </summary>
public enum ECTXOptionValues
{
CXO_SYSTEM = 0x0001,
CXO_PEN = 0x0002,
CXO_MESSAGES = 0x0004,
CXO_CSRMESSAGES = 0x0008,
CXO_MGNINSIDE = 0x4000,
CXO_MARGIN = 0x8000,
}
/// <summary>
/// Context status values.
/// </summary>
public enum ECTXStatusValues
{
CXS_DISABLED = 0x0001,
CXS_OBSCURED = 0x0002,
CXS_ONTOP = 0x0004
}
/// <summary>
/// Context lock values.
/// </summary>
public enum ECTXLockValues
{
CXL_INSIZE = 0x0001,
CXL_INASPECT = 0x0002,
CXL_SENSITIVITY = 0x0004,
CXL_MARGIN = 0x0008,
CXL_SYSOUT = 0x0010
}
/// <summary>
/// Managed version of Wintab LOGCONTEXT struct. This structure determines what events an
/// application will get, how they will be processed, and how they will be delivered to the
/// application or to Windows itself.
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct WintabLogContext
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 40)] //LCNAMELEN
public string lcName;
public UInt32 lcOptions;
public UInt32 lcStatus;
public UInt32 lcLocks;
public UInt32 lcMsgBase;
public UInt32 lcDevice;
public UInt32 lcPktRate;
public WTPKT lcPktData;
public WTPKT lcPktMode;
public WTPKT lcMoveMask;
public UInt32 lcBtnDnMask;
public UInt32 lcBtnUpMask;
public Int32 lcInOrgX;
public Int32 lcInOrgY;
public Int32 lcInOrgZ;
public Int32 lcInExtX;
public Int32 lcInExtY;
public Int32 lcInExtZ;
public Int32 lcOutOrgX;
public Int32 lcOutOrgY;
public Int32 lcOutOrgZ;
public Int32 lcOutExtX;
public Int32 lcOutExtY;
public Int32 lcOutExtZ;
public FIX32 lcSensX;
public FIX32 lcSensY;
public FIX32 lcSensZ;
public bool lcSysMode;
public Int32 lcSysOrgX;
public Int32 lcSysOrgY;
public Int32 lcSysExtX;
public Int32 lcSysExtY;
public FIX32 lcSysSensX;
public FIX32 lcSysSensY;
}
/// <summary>
/// Class to support access to Wintab context management.
/// </summary>
public class WintabContext
{
// Context data.
private WintabLogContext m_logContext = new WintabLogContext();
private HCTX m_hCTX = new HCTX(IntPtr.Zero);
public static WintabContext GetDefaultContext(EWTICategoryIndex contextIndex_I)
{
WintabContext context = new WintabContext();
int sizeOfLogContext = Marshal.SizeOf<WintabLogContext>();
IntPtr buf = Marshal.AllocHGlobal(sizeOfLogContext);
int size = (int)WintabFuncs.WTInfoA((uint)contextIndex_I, 0, buf);
context.LogContext = Marshal.PtrToStructure<WintabLogContext>(buf);
context.PktData = (uint)
(EWintabPacketBit.PK_CONTEXT |
EWintabPacketBit.PK_STATUS |
EWintabPacketBit.PK_TIME |
EWintabPacketBit.PK_CHANGED |
EWintabPacketBit.PK_SERIAL_NUMBER |
EWintabPacketBit.PK_CURSOR |
EWintabPacketBit.PK_BUTTONS |
EWintabPacketBit.PK_X |
EWintabPacketBit.PK_Y |
EWintabPacketBit.PK_Z |
EWintabPacketBit.PK_NORMAL_PRESSURE |
EWintabPacketBit.PK_TANGENT_PRESSURE |
EWintabPacketBit.PK_ORIENTATION);
context.MoveMask = context.PktData;
Marshal.FreeHGlobal(buf);
return context;
}
/// <summary>
/// Default constructor sets all data bits to be captured.
/// </summary>
public WintabContext()
{
// Init with all bits set (The Full Monty) to get all non-extended data types.
PktData = (uint)
(EWintabPacketBit.PK_CONTEXT |
EWintabPacketBit.PK_STATUS |
EWintabPacketBit.PK_TIME |
EWintabPacketBit.PK_CHANGED |
EWintabPacketBit.PK_SERIAL_NUMBER |
EWintabPacketBit.PK_CURSOR |
EWintabPacketBit.PK_BUTTONS |
EWintabPacketBit.PK_X |
EWintabPacketBit.PK_Y |
EWintabPacketBit.PK_Z |
EWintabPacketBit.PK_NORMAL_PRESSURE |
EWintabPacketBit.PK_TANGENT_PRESSURE |
EWintabPacketBit.PK_ORIENTATION);
MoveMask = PktData;
}
/// <summary>
/// Open a Wintab context to the specified hwnd.
/// </summary>
/// <param name="hwnd_I">parent window for the context</param>
/// <param name="enable_I">true to enable, false to disable</param>
/// <returns>Returns non-zero context handle if successful.</returns>
public HCTX Open(HWND hwnd_I, bool enable_I)
{
try
{
m_hCTX = new HCTX((IntPtr)WintabFuncs.WTOpenA(hwnd_I, ref m_logContext, enable_I));
}
catch (Exception ex)
{
// TODO: Throw
//MessageBox.Show("FAILED OpenContext: " + ex.ToString());
}
return m_hCTX;
}
/// <summary>
/// Open a Wintab context that will send packet events to a message window.
/// </summary>
/// <returns>Returns true if successful.</returns>
public bool Open(IntPtr hwnd)
{
m_hCTX = new HCTX((IntPtr)WintabFuncs.WTOpenA(hwnd, ref m_logContext, true));
return m_hCTX.Value != IntPtr.Zero;
}
/// <summary>
/// Close the context for this object.
/// </summary>
/// <returns>true if context successfully closed</returns>
public bool Close()
{
bool status = false;
if (m_hCTX.Value == IntPtr.Zero)
{
throw new Exception("CloseContext: invalid context");
}
status = WintabFuncs.WTClose(m_hCTX.Value);
m_hCTX = new HCTX(IntPtr.Zero);
m_logContext = new WintabLogContext();
return status;
}
/// <summary>
/// Enable/disable this Wintab context.
/// </summary>
/// <param name="enable_I">true = enable</param>
/// <returns>Returns true if completed successfully</returns>
public bool Enable(bool enable_I)
{
bool status = false;
try
{
if (m_hCTX.Value == IntPtr.Zero)
{
throw new Exception("EnableContext: invalid context");
}
status = WintabFuncs.WTEnable(m_hCTX.Value, enable_I);
}
catch (Exception ex)
{
// TODO: Throw
//MessageBox.Show("FAILED EnableContext: " + ex.ToString());
}
return status;
}
/// <summary>
/// Sends a tablet context to the top or bottom of the order of overlapping tablet contexts
/// </summary>
/// <param name="toTop_I">true = send tablet to top of order</param>
/// <returns>Returns true if successsful</returns>
public bool SetOverlapOrder(bool toTop_I)
{
bool status = false;
if (m_hCTX.Value == IntPtr.Zero)
{
throw new Exception("EnableContext: invalid context");
}
status = WintabFuncs.WTOverlap(m_hCTX.Value, toTop_I);
return status;
}
/// <summary>
/// Logical Wintab context managed by this object.
/// </summary>
public WintabLogContext LogContext { get { return m_logContext; } set { m_logContext = value; } }
/// <summary>
/// Handle (identifier) used to identify this context.
/// </summary>
public HCTX HCtx { get { return m_hCTX; } }
/// <summary>
/// Get/Set context name.
/// </summary>
public string Name { get { return m_logContext.lcName; } set { m_logContext.lcName = value; } }
/// <summary>
/// Specifies options for the context. These options can be
/// combined by using the bitwise OR operator. The lcOptions
/// field can be any combination of the values defined in
/// ECTXOptionValues.
/// </summary>
public UInt32 Options { get { return m_logContext.lcOptions; } set { m_logContext.lcOptions = value; } }
/// <summary>
/// Specifies current status conditions for the context.
/// These conditions can be combined by using the bitwise OR
/// operator. The lcStatus field can be any combination of
/// the values defined in ECTXStatusValues.
/// </summary>
public UInt32 Status { get { return m_logContext.lcStatus; } set { m_logContext.lcStatus = value; } }
/// <summary>
/// Specifies which attributes of the context the application
/// wishes to be locked. Lock conditions specify attributes
/// of the context that cannot be changed once the context
/// has been opened (calls to WTConfig will have no effect
/// on the locked attributes). The lock conditions can be
/// combined by using the bitwise OR operator. The lcLocks
/// field can be any combination of the values defined in
/// ECTXLockValues. Locks can only be changed by the task
/// or process that owns the context.
/// </summary>
public UInt32 Locks { get { return m_logContext.lcLocks; } set { m_logContext.lcLocks = value; } }
/// <summary>
/// Specifies the range of message numbers that will be used for
/// reporting the activity of the context.
/// </summary>
public UInt32 MsgBase { get { return m_logContext.lcMsgBase; } set { m_logContext.lcMsgBase = value; } }
/// <summary>
/// Specifies the device whose input the context processes.
/// </summary>
public UInt32 Device { get { return m_logContext.lcDevice; } set { m_logContext.lcDevice = value; } }
/// <summary>
/// Specifies the desired packet report rate in Hertz. Once the con-text is opened, this field will
/// contain the actual report rate.
/// </summary>
public UInt32 PktRate { get { return m_logContext.lcPktRate; } set { m_logContext.lcPktRate = value; } }
/// <summary>
/// Specifies which optional data items will be in packets returned from the context. Requesting
/// unsupported data items will cause Open() to fail.
/// </summary>
public WTPKT PktData { get { return m_logContext.lcPktData; } set { m_logContext.lcPktData = value; } }
/// <summary>
/// Specifies whether the packet data items will be returned in absolute or relative mode. If the item's
/// bit is set in this field, the item will be returned in relative mode. Bits in this field for items not
/// selected in the PktData property will be ignored. Bits for data items that only allow one mode (such
/// as the packet identifier) will also be ignored.
/// </summary>
public WTPKT PktMode { get { return m_logContext.lcPktMode; } set { m_logContext.lcPktMode = value; } }
/// <summary>
/// Specifies which packet data items can generate move events in the context. Bits for items that
/// are not part of the packet definition in the PktData property will be ignored. The bits for buttons,
/// time stamp, and the packet identifier will also be ignored. In the case of overlapping contexts, movement
/// events for data items not selected in this field may be processed by underlying contexts.
/// </summary>
public WTPKT MoveMask { get { return m_logContext.lcMoveMask; } set { m_logContext.lcMoveMask = value; } }
/// <summary>
/// Specifies the buttons for which button press events will be processed in the context. In the case of
/// overlapping contexts, button press events for buttons that are not selected in this field may be
/// processed by underlying contexts.
/// </summary>
public UInt32 BtnDnMask { get { return m_logContext.lcBtnDnMask; } set { m_logContext.lcBtnDnMask = value; } }
/// <summary>
/// Specifies the buttons for which button release events will be processed in the context. In the case
/// of overlapping contexts, button release events for buttons that are not selected in this field may be
/// processed by underlying contexts. If both press and release events are selected for a button (see the
/// BtnDnMask property), then the interface will cause the context to implicitly capture all tablet events
/// while the button is down. In this case, events occurring outside the context will be clipped to the
/// context and processed as if they had occurred in the context. When the button is released, the context
/// will receive the button release event, and then event processing will return to normal.
/// </summary>
public UInt32 BtnUpMask { get { return m_logContext.lcBtnUpMask; } set { m_logContext.lcBtnUpMask = value; } }
/// <summary>
/// Specifies the X origin of the context's input area in the tablet's native coordinates. Value is clipped
/// to the tablet native coordinate space when the context is opened or modified.
/// </summary>
public Int32 InOrgX { get { return m_logContext.lcInOrgX; } set { m_logContext.lcInOrgX = value; } }
/// <summary>
/// Specifies the Y origin of the context's input area in the tablet's native coordinates. Value is clipped
/// to the tablet native coordinate space when the context is opened or modified.
/// </summary>
public Int32 InOrgY { get { return m_logContext.lcInOrgY; } set { m_logContext.lcInOrgY = value; } }
/// <summary>
/// Specifies the Z origin of the context's input area in the tablet's native coordinates. Value is clipped
/// to the tablet native coordinate space when the context is opened or modified.
/// </summary>
public Int32 InOrgZ { get { return m_logContext.lcInOrgZ; } set { m_logContext.lcInOrgZ = value; } }
/// <summary>
/// Specifies the X extent of the context's input area in the tablet's native coordinates. Value is clipped
/// to the tablet native coordinate space when the context is opened or modified.
/// </summary>
public Int32 InExtX { get { return m_logContext.lcInExtX; } set { m_logContext.lcInExtX = value; } }
/// <summary>
/// Specifies the Y extent of the context's input area in the tablet's native coordinates. Value is clipped
/// to the tablet native coordinate space when the context is opened or modified.
/// </summary>
public Int32 InExtY { get { return m_logContext.lcInExtY; } set { m_logContext.lcInExtY = value; } }
/// <summary>
/// Specifies the Z extent of the context's input area in the tablet's native coordinates. Value is clipped
/// to the tablet native coordinate space when the context is opened or modified.
/// </summary>
public Int32 InExtZ { get { return m_logContext.lcInExtZ; } set { m_logContext.lcInExtZ = value; } }
/// <summary>
/// Specifies the X origin of the context's output area in context output coordinates. Value is used in
/// coordinate scaling for absolute mode only.
/// </summary>
public Int32 OutOrgX { get { return m_logContext.lcOutOrgX; } set { m_logContext.lcOutOrgX = value; } }
/// <summary>
/// Specifies the Y origin of the context's output area in context output coordinates. Value is used in
/// coordinate scaling for absolute mode only.
/// </summary>
public Int32 OutOrgY { get { return m_logContext.lcOutOrgY; } set { m_logContext.lcOutOrgY = value; } }
/// <summary>
/// Specifies the Z origin of the context's output area in context output coordinates. Value is used in
/// coordinate scaling for absolute mode only.
/// </summary>
public Int32 OutOrgZ { get { return m_logContext.lcOutOrgZ; } set { m_logContext.lcOutOrgZ = value; } }
/// <summary>
/// Specifies the X extent of the context's output area in context output coordinates. Value is used
/// in coordinate scaling for absolute mode only.
/// </summary>
public Int32 OutExtX { get { return m_logContext.lcOutExtX; } set { m_logContext.lcOutExtX = value; } }
/// <summary>
/// Specifies the Y extent of the context's output area in context output coordinates. Value is used
/// in coordinate scaling for absolute mode only.
/// </summary>
public Int32 OutExtY { get { return m_logContext.lcOutExtY; } set { m_logContext.lcOutExtY = value; } }
/// <summary>
/// Specifies the Z extent of the context's output area in context output coordinates. Value is used
/// in coordinate scaling for absolute mode only.
/// </summary>
public Int32 OutExtZ { get { return m_logContext.lcOutExtZ; } set { m_logContext.lcOutExtZ = value; } }
/// <summary>
/// Specifies the relative-mode sensitivity factor for the x axis.
/// </summary>
public FIX32 SensX { get { return m_logContext.lcSensX; } set { m_logContext.lcSensX = value; } }
/// <summary>
/// Specifies the relative-mode sensitivity factor for the y axis.
/// </summary>
public FIX32 SensY { get { return m_logContext.lcSensY; } set { m_logContext.lcSensY = value; } }
/// <summary>
/// Specifies the relative-mode sensitivity factor for the Z axis.
/// </summary>
public FIX32 SensZ { get { return m_logContext.lcSensZ; } set { m_logContext.lcSensZ = value; } }
/// <summary>
/// Specifies the system cursor tracking mode. Zero specifies absolute; non-zero means relative.
/// </summary>
public bool SysMode { get { return m_logContext.lcSysMode; } set { m_logContext.lcSysMode = value; } }
/// <summary>
/// Specifies the X origin of the screen mapping area for system cursor tracking, in screen coordinates.
/// </summary>
public Int32 SysOrgX { get { return m_logContext.lcSysOrgX; } set { m_logContext.lcSysOrgX = value; } }
/// <summary>
/// Specifies the Y origin of the screen mapping area for system cursor tracking, in screen coordinates.
/// </summary>
public Int32 SysOrgY { get { return m_logContext.lcSysOrgY; } set { m_logContext.lcSysOrgY = value; } }
/// <summary>
/// Specifies the X extent of the screen mapping area for system cursor tracking, in screen coordinates.
/// </summary>
public Int32 SysExtX { get { return m_logContext.lcSysExtX; } set { m_logContext.lcSysExtX = value; } }
/// <summary>
/// Specifies the Y extent of the screen mapping area for system cursor tracking, in screen coordinates.
/// </summary>
public Int32 SysExtY { get { return m_logContext.lcSysExtY; } set { m_logContext.lcSysExtY = value; } }
/// <summary>
/// Specifies the system-cursor relative-mode sensitivity factor for the x axis.
/// </summary>
public FIX32 SysSensX { get { return m_logContext.lcSysSensX; } set { m_logContext.lcSysSensX = value; } }
/// <summary>
/// Specifies the system-cursor relative-mode sensitivity factor for the y axis.
/// </summary>
public FIX32 SysSensY { get { return m_logContext.lcSysSensY; } set { m_logContext.lcSysSensY = value; } }
}
}

705
src/Windows/Avalonia.Win32/WintabImpl/WintabData.cs

@ -0,0 +1,705 @@
///////////////////////////////////////////////////////////////////////////////
//
// PURPOSE
// Wintab data management for WintabDN
//
// COPYRIGHT
// Copyright (c) 2010-2020 Wacom Co., Ltd.
//
// The text and information contained in this file may be freely used,
// copied, or distributed without compensation or licensing restrictions.
//
///////////////////////////////////////////////////////////////////////////////
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Win32.WintabImpl
{
/// <summary>
/// Wintab Packet bits.
/// </summary>
public enum EWintabPacketBit
{
PK_CONTEXT = 0x0001, /* reporting context */
PK_STATUS = 0x0002, /* status bits */
PK_TIME = 0x0004, /* time stamp */
PK_CHANGED = 0x0008, /* change bit vector */
PK_SERIAL_NUMBER = 0x0010, /* packet serial number */
PK_CURSOR = 0x0020, /* reporting cursor */
PK_BUTTONS = 0x0040, /* button information */
PK_X = 0x0080, /* x axis */
PK_Y = 0x0100, /* y axis */
PK_Z = 0x0200, /* z axis */
PK_NORMAL_PRESSURE = 0x0400, /* normal or tip pressure */
PK_TANGENT_PRESSURE = 0x0800, /* tangential or barrel pressure */
PK_ORIENTATION = 0x1000, /* orientation info: tilts */
PK_PKTBITS_ALL = 0x1FFF // The Full Monty - all the bits execept Rotation - not supported
}
/// <summary>
/// Wintab event messsages sent to an application.
/// See Wintab Spec 1.4 for a description of these messages.
/// </summary>
public enum EWintabEventMessage
{
WT_PACKET = 0x7FF0,
WT_CTXOPEN = 0x7FF1,
WT_CTXCLOSE = 0x7FF2,
WT_CTXUPDATE = 0x7FF3,
WT_CTXOVERLAP = 0x7FF4,
WT_PROXIMITY = 0x7FF5,
WT_INFOCHANGE = 0x7FF6,
WT_CSRCHANGE = 0x7FF7,
WT_PACKETEXT = 0x7FF8
}
/// <summary>
/// Wintab packet status values.
/// </summary>
public enum EWintabPacketStatusValue
{
/// <summary>
/// Specifies that the cursor is out of the context.
/// </summary>
TPS_PROXIMITY = 0x0001,
/// <summary>
/// Specifies that the event queue for the context has overflowed.
/// </summary>
TPS_QUEUE_ERR = 0x0002,
/// <summary>
/// Specifies that the cursor is in the margin of the context.
/// </summary>
TPS_MARGIN = 0x0004,
/// <summary>
/// Specifies that the cursor is out of the context, but that the
/// context has grabbed input while waiting for a button release event.
/// </summary>
TPS_GRAB = 0x0008,
/// <summary>
/// Specifies that the cursor is in its inverted state.
/// </summary>
TPS_INVERT = 0x0010
}
/// <summary>
/// WintabPacket.pkButton codes.
/// </summary>
public enum EWintabPacketButtonCode
{
/// <summary>
/// No change in button state.
/// </summary>
TBN_NONE = 0,
/// <summary>
/// Button was released.
/// </summary>
TBN_UP = 1,
/// <summary>
/// Button was pressed.
/// </summary>
TBN_DOWN = 2
}
/// <summary>
/// Pen Orientation
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct WTOrientation
{
/// <summary>
/// Specifies the clockwise rotation of the cursor about the
/// z axis through a full circular range.
/// </summary>
public Int32 orAzimuth;
/// <summary>
/// Specifies the angle with the x-y plane through a signed, semicircular range.
/// Positive values specify an angle upward toward the positive z axis; negative
/// values specify an angle downward toward the negative z axis.
/// </summary>
public Int32 orAltitude;
/// <summary>
/// Specifies the clockwise rotation of the cursor about its own major axis.
/// </summary>
public Int32 orTwist;
}
/// <summary>
/// Pen Rotation
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct WTRotation
{
/// <summary>
/// Specifies the pitch of the cursor.
/// </summary>
public Int32 rotPitch;
/// <summary>
/// Specifies the roll of the cursor.
/// </summary>
public Int32 rotRoll;
/// <summary>
/// Specifies the yaw of the cursor.
/// </summary>
public Int32 rotYaw;
}
/// <summary>
/// Wintab data packet. Contains the "Full Monty" for all possible data values.
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct WintabPacket
{
/// <summary>
/// Specifies the context that generated the event.
/// </summary>
public HCTX pkContext; // PK_CONTEXT
/// <summary>
/// Specifies various status and error conditions. These conditions can be
/// combined by using the bitwise OR opera-tor. The pkStatus field can be any
/// any combination of the values defined in EWintabPacketStatusValue.
/// </summary>
public UInt32 pkStatus; // PK_STATUS
/// <summary>
/// In absolute mode, specifies the system time at which the event was posted. In
/// relative mode, specifies the elapsed time in milliseconds since the last packet.
/// </summary>
public UInt32 pkTime; // PK_TIME
/// <summary>
/// Specifies which of the included packet data items have changed since the
/// previously posted event.
/// </summary>
public WTPKT pkChanged; // PK_CHANGED
/// <summary>
/// This is an identifier assigned to the packet by the context. Consecutive
/// packets will have consecutive serial numbers.
/// </summary>
public UInt32 pkSerialNumber; // PK_SERIAL_NUMBER
/// <summary>
/// Specifies which cursor type generated the packet.
/// </summary>
public UInt32 pkCursor; // PK_CURSOR
/// <summary>
/// In absolute mode, is a UInt32 containing the current button state.
/// In relative mode, is a UInt32 whose low word contains a button number,
/// and whose high word contains one of the codes in EWintabPacketButtonCode.
/// </summary>
public UInt32 pkButtons; // PK_BUTTONS
/// <summary>
/// In absolute mode, each is a UInt32 containing the scaled cursor location
/// along the X axis. In relative mode, this is an Int32 containing
/// scaled change in cursor position.
/// </summary>
public Int32 pkX; // PK_X
/// <summary>
/// In absolute mode, each is a UInt32 containing the scaled cursor location
/// along the Y axis. In relative mode, this is an Int32 containing
/// scaled change in cursor position.
/// </summary>
public Int32 pkY; // PK_Y
/// <summary>
/// In absolute mode, each is a UInt32 containing the scaled cursor location
/// along the Z axis. In relative mode, this is an Int32 containing
/// scaled change in cursor position.
/// </summary>
public Int32 pkZ; // PK_Z
/// <summary>
/// In absolute mode, this is a UINT containing the adjusted state
/// of the normal pressure, respectively. In relative mode, this is
/// an int containing the change in adjusted pressure state.
/// </summary>
public UInt32 pkNormalPressure; // PK_NORMAL_PRESSURE
/// <summary>
/// In absolute mode, this is a UINT containing the adjusted state
/// of the tangential pressure, respectively. In relative mode, this is
/// an int containing the change in adjusted pressure state.
/// </summary>
public UInt32 pkTangentPressure; // PK_TANGENT_PRESSURE
/// <summary>
/// Contains updated cursor orientation information. See the
/// WTOrientation structure for details.
/// </summary>
public WTOrientation pkOrientation; // ORIENTATION
public static int ByteSize { get; } = Marshal.SizeOf<WintabPacket>();
}
/// <summary>
/// Common properties for control extension data transactions.
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct WTExtensionBase
{
/// <summary>
/// Specifies the Wintab context to which these properties apply.
/// </summary>
public HCTX nContext;
/// <summary>
/// Status of setting/getting properties.
/// </summary>
public UInt32 nStatus;
/// <summary>
/// Timestamp applied to property transaction.
/// </summary>
public WTPKT nTime;
/// <summary>
/// Reserved - not used.
/// </summary>
public UInt32 nSerialNumber;
}
/// <summary>
/// Extension data for one Express Key.
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct WTExpKeyData
{
/// <summary>
/// Tablet index where control is found.
/// </summary>
public byte nTablet;
/// <summary>
/// Zero-based control index.
/// </summary>
public byte nControl;
/// <summary>
/// Zero-based index indicating side of tablet where control found (0 = left, 1 = right).
/// </summary>
public byte nLocation;
/// <summary>
/// Reserved - not used
/// </summary>
public byte nReserved;
/// <summary>
/// Indicates Express Key button press (1 = pressed, 0 = released)
/// </summary>
public WTPKT nState;
}
/// <summary>
/// Extension data for one touch ring or one touch strip.
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct WTSliderData
{
/// <summary>
/// Tablet index where control is found.
/// </summary>
public byte nTablet;
/// <summary>
/// Zero-based control index.
/// </summary>
public byte nControl;
/// <summary>
/// Zero-based current active mode of control.
/// This is the mode selected by control's toggle button.
/// </summary>
public byte nMode;
/// <summary>
/// Reserved - not used
/// </summary>
public byte nReserved;
/// <summary>
/// An integer representing the position of the user's finger on the control.
/// When there is no finger on the control, this value is negative.
/// </summary>
public WTPKT nPosition;
}
/// <summary>
/// Wintab extension data packet for one tablet control.
/// The tablet controls for which extension data is available are: Express Key, Touch Ring and Touch Strip controls.
/// Note that tablets will have either Touch Rings or Touch Strips - not both.
/// All tablets have Express Keys.
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct WintabPacketExt
{
/// <summary>
/// Extension control properties common to all control types.
/// </summary>
public WTExtensionBase pkBase;
/// <summary>
/// Extension data for one Express Key.
/// </summary>
public WTExpKeyData pkExpKey;
/// <summary>
/// Extension data for one Touch Strip.
/// </summary>
public WTSliderData pkTouchStrip;
/// <summary>
/// Extension data for one Touch Ring.
/// </summary>
public WTSliderData pkTouchRing;
}
/// <summary>
/// Class to support capture and management of Wintab daa.
/// </summary>
public class WintabData
{
private WintabContext m_context;
/// <summary>
/// CWintabData constructor
/// </summary>
/// <param name="context_I">logical context for this data object</param>
public WintabData(WintabContext context_I)
{
m_context = context_I;
}
/*
/// <summary>
/// Removes the WTPacket handler so that messages are ignored.
/// </summary>
/// <param name="handler_I">WT_PACKET event handler supplied by the client.</param>
public void RemoveWTPacketEventHandler(EventHandler<MessageReceivedEventArgs> handler_I)
{
try
{
MessageEvents.UnWatchMessage(EWintabEventMessage.WT_PACKET);
MessageEvents.UnWatchMessage(EWintabEventMessage.WT_PACKETEXT);
MessageEvents.UnWatchMessage(EWintabEventMessage.WT_CSRCHANGE);
MessageEvents.PacketMessageReceived -= handler_I;
}
catch (Exception ex)
{
MessageBox.Show("FAILED CWintabData.RemoveWTPacketEventHandler: " + ex.ToString());
}
}
/// <summary>
/// Set the handler to be called when WT_CTX* messages are received.
/// </summary>
/// <param name="handler_I">WT_CTX* event handler supplied by the client.</param>
public void SetStatusEventHandler(EventHandler<MessageReceivedEventArgs> handler_I)
{
try
{
MessageEvents.StatusMessageReceived += handler_I;
MessageEvents.WatchMessage(EWintabEventMessage.WT_CTXOPEN);
MessageEvents.WatchMessage(EWintabEventMessage.WT_CTXCLOSE);
MessageEvents.WatchMessage(EWintabEventMessage.WT_CTXUPDATE);
MessageEvents.WatchMessage(EWintabEventMessage.WT_CTXOVERLAP);
MessageEvents.WatchMessage(EWintabEventMessage.WT_PROXIMITY);
}
catch (Exception ex)
{
MessageBox.Show("FAILED CWintabData.SetStatusEventHandler: " + ex.ToString());
}
}
/// <summary>
/// Removes the WT_CTX* handler so that messages are ignored.
/// </summary>
/// <param name="handler_I"> WT_CTX* event handler supplied by the client.</param>
public void RemoveStatusEventHandler(EventHandler<MessageReceivedEventArgs> handler_I)
{
try
{
MessageEvents.UnWatchMessage(EWintabEventMessage.WT_CTXOPEN);
MessageEvents.UnWatchMessage(EWintabEventMessage.WT_CTXCLOSE);
MessageEvents.UnWatchMessage(EWintabEventMessage.WT_CTXUPDATE);
MessageEvents.UnWatchMessage(EWintabEventMessage.WT_CTXOVERLAP);
MessageEvents.UnWatchMessage(EWintabEventMessage.WT_PROXIMITY);
MessageEvents.StatusMessageReceived -= handler_I;
}
catch (Exception ex)
{
MessageBox.Show("FAILED CWintabData.RemoveStatusEventHandler: " + ex.ToString());
}
}
/// <summary>
/// Set the handler to be called when WT_INFOCHANGE messages are received.
/// </summary>
/// <param name="handler_I">WT_INFOCHANGE event handler supplied by the client.</param>
public void SetInfoChangeEventHandler(EventHandler<MessageReceivedEventArgs> handler_I)
{
try
{
MessageEvents.InfoChgMessageReceived += handler_I;
MessageEvents.WatchMessage(EWintabEventMessage.WT_INFOCHANGE);
}
catch (Exception ex)
{
MessageBox.Show("FAILED CWintabData.SetWTInfoChangeEventHandler: " + ex.ToString());
}
}
/// <summary>
/// Removes the WT_INFOCHANGE handler so that messages are ignored.
/// </summary>
/// <param name="handler_I">WT_INFOCHANGE event handler supplied by the client.</param>
public void RemoveInfoChangeEventHandler(EventHandler<MessageReceivedEventArgs> handler_I)
{
try
{
MessageEvents.UnWatchMessage(EWintabEventMessage.WT_INFOCHANGE);
MessageEvents.InfoChgMessageReceived -= handler_I;
}
catch (Exception ex)
{
MessageBox.Show("FAILED CWintabData.RemoveWTInfoChangeEventHandler: " + ex.ToString());
}
}
*/
/// <summary>
/// Set packet queue size for this data object's context.
/// </summary>
/// <param name="numPkts_I">desired #packets in queue</param>
/// <returns>Returns true if operation successful</returns>
public bool SetPacketQueueSize(UInt32 numPkts_I)
{
bool status = false;
CheckForValidHCTX("SetPacketQueueSize");
status = WintabFuncs.WTQueueSizeSet(m_context.HCtx, numPkts_I);
return status;
}
/// <summary>
/// Get packet queue size for this data object's context.
/// </summary>
/// <returns>Returns a packet queue size in #packets or 0 if fails</returns>
public UInt32 GetPacketQueueSize()
{
UInt32 numPkts = 0;
CheckForValidHCTX("GetPacketQueueSize");
numPkts = WintabFuncs.WTQueueSizeGet(m_context.HCtx);
return numPkts;
}
public void BringToFront()
{
CheckForValidHCTX("BringToFront");
WintabFuncs.WTOverlap(m_context.HCtx, true);
}
/// <summary>
/// Returns one packet of WintabPacketExt data from the packet queue.
/// </summary>
/// <param name="hCtx">Wintab context to be used when asking for the data</param>
/// <param name="pktID_I">Identifier for the tablet event packet to return.</param>
/// <returns>Returns a data packet with non-null context if successful.</returns>
public WintabPacketExt GetDataPacketExt(IntPtr hCtx, UInt32 pktID_I)
{
int size = (int)(Marshal.SizeOf(new WintabPacketExt()));
IntPtr buf = Marshal.AllocHGlobal(size);
WintabPacketExt[] packets = [];
bool status = false;
if (pktID_I == 0)
{
throw new Exception("GetDataPacket - invalid pktID");
}
CheckForValidHCTX("GetDataPacket");
status = WintabFuncs.WTPacket(hCtx, pktID_I, buf);
if (status)
{
packets = WintabMemUtils.MarshalDataExtPackets(1, buf);
}
else
{
// If fails, make sure context is zero.
packets[0].pkBase.nContext = new HCTX(IntPtr.Zero);
}
Marshal.FreeHGlobal(buf);
return packets[0];
}
/// <summary>
/// Returns one packet of WintabPacket data from the packet queue. (Deprecated)
/// </summary>
/// <param name="pktID_I">Identifier for the tablet event packet to return.</param>
/// <returns>Returns a data packet with non-null context if successful.</returns>
public WintabPacket GetDataPacket(UInt32 pktID_I)
{
return GetDataPacket(m_context.HCtx, pktID_I);
}
/// <summary>
/// Returns one packet of Wintab data from the packet queue.
/// </summary>
/// <param name="hCtx">Wintab context to be used when asking for the data</param>
/// <param name="pktID_I">Identifier for the tablet event packet to return.</param>
/// <returns>Returns a data packet with non-null context if successful.</returns>
public WintabPacket GetDataPacket(IntPtr hCtx, UInt32 pktID_I)
{
IntPtr buf = Marshal.AllocHGlobal(Marshal.SizeOf<WintabPacket>());
WintabPacket packet = new WintabPacket();
if (pktID_I == 0)
{
throw new Exception("GetDataPacket - invalid pktID");
}
CheckForValidHCTX("GetDataPacket");
if (WintabFuncs.WTPacket(hCtx, pktID_I, buf))
{
packet = Marshal.PtrToStructure<WintabPacket>(buf);
}
else
{
//
// If fails, make sure context is zero.
//
packet.pkContext = new HCTX(IntPtr.Zero);
}
Marshal.FreeHGlobal(buf);
return packet;
}
/// <summary>
/// Removes all pending data packets from the context's queue.
/// </summary>
public void FlushDataPackets(uint numPacketsToFlush_I)
{
CheckForValidHCTX("FlushDataPackets");
WintabFuncs.WTPacketsGet(m_context.HCtx, numPacketsToFlush_I, IntPtr.Zero);
}
/// <summary>
/// Returns an array of Wintab data packets from the packet queue.
/// </summary>
/// <param name="maxPkts_I">Specifies the maximum number of packets to return.</param>
/// <param name="remove_I">If true, returns data packets and removes them from the queue.</param>
/// <param name="numPkts_O">Number of packets actually returned.</param>
/// <returns>Returns the next maxPkts_I from the list. Note that if remove_I is false, then
/// repeated calls will return the same packets. If remove_I is true, then packets will be
/// removed and subsequent calls will get different packets (if any).</returns>
public WintabPacket[] GetDataPackets(UInt32 maxPkts_I, bool remove_I, ref UInt32 numPkts_O)
{
WintabPacket[] packets = [];
CheckForValidHCTX("GetDataPackets");
if (maxPkts_I == 0)
{
throw new Exception("GetDataPackets - maxPkts_I is zero.");
}
// Packet array is used whether we're just looking or buying.
int size = (int)(maxPkts_I * Marshal.SizeOf(new WintabPacket()));
IntPtr buf = Marshal.AllocHGlobal(size);
try
{
if (remove_I)
{
// Return data packets and remove packets from queue.
numPkts_O = WintabFuncs.WTPacketsGet(m_context.HCtx, maxPkts_I, buf);
if (numPkts_O > 0)
{
packets = WintabMemUtils.MarshalDataPackets(numPkts_O, buf);
}
}
else
{
// Return data packets, but leave on queue. (Peek mode)
UInt32 pktIDOldest = 0;
UInt32 pktIDNewest = 0;
// Get oldest and newest packet identifiers in the queue. These will bound the
// packets that are actually returned.
if (WintabFuncs.WTQueuePacketsEx(m_context.HCtx, ref pktIDOldest, ref pktIDNewest))
{
UInt32 pktIDStart = pktIDOldest;
UInt32 pktIDEnd = pktIDNewest;
if (pktIDStart == 0)
{
throw new Exception("WTQueuePacketsEx reports zero start packet identifier");
}
if (pktIDEnd == 0)
{
throw new Exception("WTQueuePacketsEx reports zero end packet identifier");
}
// Peek up to the max number of packets specified.
UInt32 numFoundPkts = WintabFuncs.WTDataPeek(m_context.HCtx, pktIDStart, pktIDEnd, maxPkts_I,
buf,
ref numPkts_O);
if (numFoundPkts > 0 && numFoundPkts < numPkts_O)
{
throw new Exception(
"WTDataPeek reports more packets returned than actually exist in queue.");
}
packets = WintabMemUtils.MarshalDataPackets(numPkts_O, buf);
}
}
}
finally
{
Marshal.FreeHGlobal(buf);
}
return packets;
}
/// <summary>
/// Throws exception if logical context for this data object is zero.
/// </summary>
private void CheckForValidHCTX(string msg)
{
if (m_context.HCtx == 0)
{
throw new Exception(msg + " - Bad Context");
}
}
}
}

484
src/Windows/Avalonia.Win32/WintabImpl/WintabFuncs.cs

@ -0,0 +1,484 @@
///////////////////////////////////////////////////////////////////////////////
//
// PURPOSE
// Wintab32 function wrappers for WintabDN
//
// COPYRIGHT
// Copyright (c) 2010-2020 Wacom Co., Ltd.
//
// The text and information contained in this file may be freely used,
// copied, or distributed without compensation or licensing restrictions.
//
///////////////////////////////////////////////////////////////////////////////
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Win32.WintabImpl
{
using P_HCTX = System.IntPtr;
using P_HWND = System.IntPtr;
//Implementation note: cannot use statement such as:
// using WTPKT = UInt32;
// because the scope of the statement is this file only.
// Thus we need to implement the 'typedef' using a class that
// implicitly defines the type. Also remember to make it
// sequential so it won't make marshalling barf.
/// <summary>
/// Managed implementation of Wintab HWND typedef.
/// Holds native Window handle.
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct HWND
{
[MarshalAs(UnmanagedType.I4)] public IntPtr value;
public HWND(IntPtr value)
{
this.value = value;
}
public static implicit operator IntPtr(HWND hwnd_I)
{
return hwnd_I.value;
}
public static implicit operator HWND(IntPtr ptr_I)
{
return new HWND(ptr_I);
}
public static bool operator ==(HWND hwnd1, HWND hwnd2)
{
return hwnd1.value == hwnd2.value;
}
public static bool operator !=(HWND hwnd1, HWND hwnd2)
{
return hwnd1.value != hwnd2.value;
}
public override bool Equals(object? obj)
{
if (obj == null || obj.GetType() != typeof(HWND))
return false;
return (HWND)obj == this;
}
public override int GetHashCode()
{
return 0;
}
}
/// <summary>
/// Managed implementation of Wintab WTPKT typedef.
/// Holds Wintab packet identifier.
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class WTPKT
{
[MarshalAs(UnmanagedType.U4)] UInt32 value;
public WTPKT(UInt32 value)
{
this.value = value;
}
public static implicit operator UInt32(WTPKT pkt_I)
{
return pkt_I.value;
}
public static implicit operator WTPKT(UInt32 value)
{
return new WTPKT(value);
}
public override string ToString()
{
return value.ToString();
}
}
/// <summary>
/// Managed implementation of Wintab FIX32 typedef.
/// Used for a fixed-point arithmetic value.
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class FIX32
{
[MarshalAs(UnmanagedType.U4)] UInt32 value;
public FIX32(UInt32 value)
{
this.value = value;
}
public static implicit operator UInt32(FIX32 fix32_I)
{
return fix32_I.value;
}
public static implicit operator FIX32(UInt32 value)
{
return new FIX32(value);
}
public double FixToDouble()
{
uint x = this;
ushort integerPart = (ushort)(x >> 16);
ushort fractionalPart = (ushort)(x & 0xFFFF);
return integerPart + (fractionalPart / 65536.0);
}
public static double Frac(double x)
{
return x - Math.Floor(x);
}
public override string ToString()
{
return value.ToString();
}
}
/// <summary>
/// Managed implementation of Wintab HCTX typedef.
/// Holds a Wintab context identifier.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public readonly struct HCTX : IEquatable<HCTX>
{
public readonly IntPtr Value;
public HCTX(IntPtr value)
{
Value = value;
}
public bool Equals(HCTX other) => Value == other.Value;
public override bool Equals(object? obj) => obj is HCTX other && Equals(other);
public override int GetHashCode() => Value.GetHashCode();
public static bool operator ==(HCTX a, HCTX b) => a.Value == b.Value;
public static bool operator !=(HCTX a, HCTX b) => a.Value != b.Value;
public static implicit operator IntPtr(HCTX hctx_I)
{
return hctx_I.Value;
}
public override string ToString()
{
return Value.ToString();
}
}
/// <summary>
/// Index values for WTInfo wCategory parameter.
/// </summary>
public enum EWTICategoryIndex
{
WTI_INTERFACE = 1,
WTI_STATUS = 2,
WTI_DEFCONTEXT = 3,
WTI_DEFSYSCTX = 4,
WTI_DEVICES = 100,
WTI_CURSORS = 200,
WTI_EXTENSIONS = 300,
WTI_DDCTXS = 400,
WTI_DSCTXS = 500
}
/// <summary>
/// Index values for WTI_INTERFACE.
/// </summary>
public enum EWTIInterfaceIndex
{
IFC_WINTABID = 1,
IFC_SPECVERSION = 2,
IFC_IMPLVERSION = 3,
IFC_NDEVICES = 4,
IFC_NCURSORS = 5,
IFC_NCONTEXTS = 6,
IFC_CTXOPTIONS = 7,
IFC_CTXSAVESIZE = 8,
IFC_NEXTENSIONS = 9,
IFC_NMANAGERS = 10
}
/// <summary>
/// Index values for WTI_DEVICES
/// </summary>
public enum EWTIDevicesIndex
{
DVC_NAME = 1,
DVC_HARDWARE = 2,
DVC_NCSRTYPES = 3,
DVC_FIRSTCSR = 4,
DVC_PKTRATE = 5,
DVC_PKTDATA = 6,
DVC_PKTMODE = 7,
DVC_CSRDATA = 8,
DVC_XMARGIN = 9,
DVC_YMARGIN = 10,
DVC_ZMARGIN = 11,
DVC_X = 12,
DVC_Y = 13,
DVC_Z = 14,
DVC_NPRESSURE = 15,
DVC_TPRESSURE = 16,
DVC_ORIENTATION = 17,
DVC_ROTATION = 18,
DVC_PNPID = 19
}
/// <summary>
/// Index values for WTI_CURSORS.
/// </summary>
public enum EWTICursorsIndex
{
CSR_NAME = 1,
CSR_ACTIVE = 2,
CSR_PKTDATA = 3,
CSR_BUTTONS = 4,
CSR_BUTTONBITS = 5,
CSR_BTNNAMES = 6,
CSR_BUTTONMAP = 7,
CSR_SYSBTNMAP = 8,
CSR_NPBUTTON = 9,
CSR_NPBTNMARKS = 10,
CSR_NPRESPONSE = 11,
CSR_TPBUTTON = 12,
CSR_TPBTNMARKS = 13,
CSR_TPRESPONSE = 14,
CSR_PHYSID = 15,
CSR_MODE = 16,
CSR_MINPKTDATA = 17,
CSR_MINBUTTONS = 18,
CSR_CAPABILITIES = 19,
CSR_TYPE = 20
}
/// <summary>
/// Index used with CSR_NAME to get stylus types.
/// </summary>
public enum EWTICursorNameIndex
{
CSR_NAME_PUCK = EWTICategoryIndex.WTI_CURSORS + 0,
CSR_NAME_PRESSURE_STYLUS = EWTICategoryIndex.WTI_CURSORS + 1,
CSR_NAME_ERASER = EWTICategoryIndex.WTI_CURSORS + 2
}
/// <summary>
/// Index values for WTI contexts.
/// </summary>
public enum EWTIContextIndex
{
CTX_NAME = 1,
CTX_OPTIONS = 2,
CTX_STATUS = 3,
CTX_LOCKS = 4,
CTX_MSGBASE = 5,
CTX_DEVICE = 6,
CTX_PKTRATE = 7,
CTX_PKTDATA = 8,
CTX_PKTMODE = 9,
CTX_MOVEMASK = 10,
CTX_BTNDNMASK = 11,
CTX_BTNUPMASK = 12,
CTX_INORGX = 13,
CTX_INORGY = 14,
CTX_INORGZ = 15,
CTX_INEXTX = 16,
CTX_INEXTY = 17,
CTX_INEXTZ = 18,
CTX_OUTORGX = 19,
CTX_OUTORGY = 20,
CTX_OUTORGZ = 21,
CTX_OUTEXTX = 22,
CTX_OUTEXTY = 23,
CTX_OUTEXTZ = 24,
CTX_SENSX = 25,
CTX_SENSY = 26,
CTX_SENSZ = 27,
CTX_SYSMODE = 28,
CTX_SYSORGX = 29,
CTX_SYSORGY = 30,
CTX_SYSEXTX = 31,
CTX_SYSEXTY = 32,
CTX_SYSSENSX = 33,
CTX_SYSSENSY = 34
}
/// <summary>
/// P/Invoke wrappers for Wintab functions.
/// See Wintab_v140.doc (Wintab 1.4 spec) and related Wintab documentation for details.
/// </summary>
public class WintabFuncs
{
/// <summary>
/// This function returns global information about the interface in an application-supplied buffer.
/// Different types of information are specified by different index arguments. Applications use this
/// function to receive information about tablet coordinates, physical dimensions, capabilities, and
/// cursor types.
/// </summary>
/// <param name="wCategory_I">Identifies the category from which information is being requested.</param>
/// <param name="nIndex_I">Identifies which information is being requested from within the category.</param>
/// <param name="lpOutput_O">Points to a buffer to hold the requested information.</param>
/// <returns>The return value specifies the size of the returned information in bytes. If the information
/// is not supported, the function returns zero. If a tablet is not physically present, this function
/// always returns zero.
/// </returns>
[DllImport("Wintab32.dll", CharSet = CharSet.Auto)]
public static extern UInt32 WTInfoA(UInt32 wCategory_I, UInt32 nIndex_I, IntPtr lpOutput_O);
/// <summary>
/// This function establishes an active context on the tablet. On successful completion of this function,
/// the application may begin receiving tablet events via messages (if they were requested), and may use
/// the handle returned to poll the context, or to perform other context-related functions.
/// </summary>
/// <param name="hWnd_I">Identifies the window that owns the tablet context, and receives messages from the context.</param>
/// <param name="logContext_I">Points to an application-provided WintabLogContext data structure describing the context to be opened.</param>
/// <param name="enable_I">Specifies whether the new context will immediately begin processing input data.</param>
/// <returns>The return value identifies the new context. It is NULL if the context is not opened.</returns>
[DllImport("Wintab32.dll", CharSet = CharSet.Auto)]
public static extern P_HCTX WTOpenA(P_HWND hWnd_I, ref WintabLogContext logContext_I, bool enable_I);
/// <summary>
/// This function closes and destroys the tablet context object.
/// </summary>
/// <param name="hctx_I">Identifies the context to be closed.</param>
/// <returns>The function returns a non-zero value if the context was valid and was destroyed. Otherwise, it returns zero.</returns>
[DllImport("Wintab32.dll", CharSet = CharSet.Auto)]
public static extern bool WTClose(P_HCTX hctx_I);
/// <summary>
/// This function enables or disables a tablet context, temporarily turning on or off the processing of packets.
/// </summary>
/// <param name="hctx_I">Identifies the context to be enabled or disabled.</param>
/// <param name="enable_I">Specifies enabling if non-zero, disabling if zero.</param>
/// <returns>The function returns true if the enable or disable request was satisfied.</returns>
[DllImport("Wintab32.dll", CharSet = CharSet.Auto)]
public static extern bool WTEnable(P_HCTX hctx_I, bool enable_I);
/// <summary>
/// This function sends a tablet context to the top or bottom of the order of overlapping tablet contexts.
/// </summary>
/// <param name="hctx_I">Identifies the context to move within the overlap order.</param>
/// <param name="toTop_I">Specifies sending the context to the top of the overlap order true, or to the bottom if false.</param>
/// <returns>The function returns true if successful.</returns>
[DllImport("Wintab32.dll", CharSet = CharSet.Auto)]
public static extern bool WTOverlap(P_HCTX hctx_I, bool toTop_I);
/// <summary>
/// This function returns the number of packets the context's queue can hold.
/// </summary>
/// <param name="hctx_I">Identifies the context whose queue size is being returned.</param>
/// <returns>The number of packets the queue can hold.</returns>
[DllImport("Wintab32.dll", CharSet = CharSet.Auto)]
public static extern UInt32 WTQueueSizeGet(P_HCTX hctx_I);
/// <summary>
/// This function attempts to change the context's queue size to the value specified in nPkts_I.
/// </summary>
/// <param name="hctx_I">Identifies the context whose queue size is being set.</param>
/// <param name="nPkts_I">Specifies the requested queue size.</param>
/// <returns>The return value is true if the queue size was successfully changed.</returns>
[DllImport("Wintab32.dll", CharSet = CharSet.Auto)]
public static extern bool WTQueueSizeSet(P_HCTX hctx_I, UInt32 nPkts_I);
/// <summary>
/// This function fills in the passed pktBuf_O buffer with the context event packet having
/// the specified serial number. The returned packet and any older packets are removed from
/// the context's internal queue.
/// </summary>
/// <param name="hctx_I">Identifies the context whose packets are being returned.</param>
/// <param name="pktSerialNum_I">Serial number of the tablet event to return.</param>
/// <param name="pktBuf_O">Buffer to receive the event packet.</param>
/// <returns>The return value is true if the specified packet was found and returned.
/// It is false if the specified packet was not found in the queue.</returns>
[DllImport("Wintab32.dll", CharSet = CharSet.Auto)]
public static extern bool WTPacket(P_HCTX hctx_I, UInt32 pktSerialNum_I, IntPtr pktBuf_O);
/// <summary>
/// This function copies the next maxPkts_I events from the packet queue of context hCtx to
/// the passed pktBuf_O buffer and removes them from the queue
/// </summary>
/// <param name="hctx_I">Identifies the context whose packets are being returned.</param>
/// <param name="maxPkts_I">Specifies the maximum number of packets to return</param>
/// <param name="pktBuf_O">Buffer to receive the event packets.</param>
/// <returns>The return value is the number of packets copied in the buffer.</returns>
[DllImport("Wintab32.dll", CharSet = CharSet.Auto)]
public static extern UInt32 WTPacketsGet(P_HCTX hctx_I, UInt32 maxPkts_I, IntPtr pktBuf_O);
/// <summary>A
/// This function copies all packets with Identifiers between pktIDStart_I and pktIDEnd_I
/// inclusive from the context's queue to the passed buffer and removes them from the queue.
/// </summary>
/// <param name="hctx_I">Identifies the context whose packets are being returned.</param>
/// <param name="pktIDStart_I">Identifier of the oldest tablet event to return.</param>
/// <param name="pktIDEnd_I">Identifier of the newest tablet event to return.</param>
/// <param name="maxPkts_I">Specifies the maximum number of packets to return.</param>
/// <param name="pktBuf_O">Buffer to receive the event packets.</param>
/// <param name="numPkts_O">Number of packets actually copied.</param>
/// <returns>The return value is the total number of packets found in the queue
/// between pktIDStart_I and pktIDEnd_I.</returns>
[DllImport("Wintab32.dll", CharSet = CharSet.Auto)]
public static extern UInt32 WTDataGet(P_HCTX hctx_I, UInt32 pktIDStart_I, UInt32 pktIDEnd_I,
UInt32 maxPkts_I, IntPtr pktBuf_O, ref UInt32 numPkts_O);
/// <summary>
/// This function copies all packets with serial numbers between pktIDStart_I and pktIDEnd_I
/// inclusive, from the context's queue to the passed buffer without removing them from the queue.
/// </summary>
/// <param name="hctx_I">Identifies the context whose packets are being read.</param>
/// <param name="pktIDStart_I">Identifier of the oldest tablet event to return.</param>
/// <param name="pktIDEnd_I">Identifier of the newest tablet event to return.</param>
/// <param name="maxPkts_I">Specifies the maximum number of packets to return.</param>
/// <param name="pktBuf_O">Buffer to receive the event packets.</param>
/// <param name="numPkts_O">Number of packets actually copied.</param>
/// <returns>The return value is the total number of packets found in the queue between
/// pktIDStart_I and pktIDEnd_I.</returns>
[DllImport("Wintab32.dll", CharSet = CharSet.Auto)]
public static extern UInt32 WTDataPeek(P_HCTX hctx_I, UInt32 pktIDStart_I, UInt32 pktIDEnd_I,
UInt32 maxPkts_I, IntPtr pktBuf_O, ref UInt32 numPkts_O);
/// <summary>
/// This function returns the identifiers of the oldest and newest packets currently in the queue.
/// </summary>
/// <param name="hctx_I">Identifies the context whose queue is being queried.</param>
/// <param name="pktIDOldest_O">Identifier of the oldest packet in the queue.</param>
/// <param name="pktIDNewest_O">Identifier of the newest packet in the queue.</param>
/// <returns>This function returns bool if successful.</returns>
[DllImport("Wintab32.dll", CharSet = CharSet.Auto)]
public static extern bool WTQueuePacketsEx(P_HCTX hctx_I, ref UInt32 pktIDOldest_O, ref UInt32 pktIDNewest_O);
/// <summary>
/// This function retrieves any context-specific data for an extension.
/// </summary>
/// <param name="hctx_I">Identifies the context whose extension attributes are being retrieved.</param>
/// <param name="extTag_I">Identifies the extension tag for which context-specific data is being retrieved.</param>
/// <param name="extData_O">Points to a buffer to hold retrieved data (WTExtensionProperty).</param>
/// <returns></returns>
[DllImport("Wintab32.dll", CharSet = CharSet.Auto)]
public static extern bool WTExtGet(P_HCTX hctx_I, UInt32 extTag_I, IntPtr extData_O);
/// <summary>
/// This function sets any context-specific data for an extension.
/// </summary>
/// <param name="hctx_I">Identifies the context whose extension attributes are being modified.</param>
/// <param name="extTag_I">Identifies the extension tag for which context-specific data is being modified.</param>
/// <param name="extData_I">Points to the new data (WTExtensionProperty).</param>
/// <returns></returns>
[DllImport("Wintab32.dll", CharSet = CharSet.Auto)]
public static extern bool WTExtSet(P_HCTX hctx_I, UInt32 extTag_I, IntPtr extData_I);
}
}

512
src/Windows/Avalonia.Win32/WintabImpl/WintabInfo.cs

@ -0,0 +1,512 @@
///////////////////////////////////////////////////////////////////////////////
//
// PURPOSE
// Wintab information access for WintabDN
//
// COPYRIGHT
// Copyright (c) 2010-2020 Wacom Co., Ltd.
//
// The text and information contained in this file may be freely used,
// copied, or distributed without compensation or licensing restrictions.
//
///////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Avalonia.Win32.WintabImpl
{
/// <summary>
/// Class to access Wintab interface data.
/// </summary>
public class WintabInfo
{
public const Int32 MAX_STRING_SIZE = 256;
public const Int32 MAX_NUM_ATTACHED_TABLETS = 16;
public const Int32 MAX_NUM_CURSORS = 6;
/// <summary>
/// Returns TRUE if Wintab service is running and responsive.
/// </summary>
/// <returns></returns>
public static bool IsWintabAvailable()
{
try
{
IntPtr buf = IntPtr.Zero;
var status = (WintabFuncs.WTInfoA(0, 0, buf) > 0);
return status;
}
catch
{
return false;
}
}
/// <summary>
/// Return max normal pressure supported by tablet.
/// </summary>
/// <param name="getNormalPressure_I">TRUE=> normal pressure;
/// FALSE=> tangential pressure (not supported on all tablets)</param>
/// <returns>maximum pressure value or zero on error</returns>
public static Int32 GetMaxPressure(bool getNormalPressure_I = true)
{
WintabAxis pressureAxis = new WintabAxis();
int numBytes = Marshal.SizeOf(pressureAxis);
IntPtr buf = Marshal.AllocHGlobal(numBytes);
EWTIDevicesIndex devIdx = (getNormalPressure_I ?
EWTIDevicesIndex.DVC_NPRESSURE :
EWTIDevicesIndex.DVC_TPRESSURE);
int size = (int)WintabFuncs.WTInfoA(
(uint)EWTICategoryIndex.WTI_DEVICES,
(uint)devIdx, buf);
pressureAxis = Marshal.PtrToStructure<WintabAxis>(buf);
Marshal.FreeHGlobal(buf);
return pressureAxis.axMax;
}
/// <summary>
/// Returns a 3-element array describing the tablet's orientation range and resolution capabilities.
/// </summary>
/// <returns></returns>
public static WintabAxisArray GetDeviceOrientation(out bool tiltSupported_O)
{
WintabAxisArray axisArray = new WintabAxisArray();
tiltSupported_O = false;
IntPtr buf = Marshal.AllocHGlobal(Marshal.SizeOf(axisArray));
int size = (int)WintabFuncs.WTInfoA(
(uint)EWTICategoryIndex.WTI_DEVICES,
(uint)EWTIDevicesIndex.DVC_ORIENTATION, buf);
// If size == 0, then returns a zeroed struct.
axisArray = Marshal.PtrToStructure<WintabAxisArray>(buf);
tiltSupported_O = (axisArray.array[0].axResolution != 0 && axisArray.array[1].axResolution != 0);
Marshal.FreeHGlobal(buf);
return axisArray;
}
/*/// <summary>
/// Returns a string containing device name.
/// </summary>
/// <returns></returns>
public static string GetDeviceInfo()
{
string devInfo = null;
IntPtr buf = CMemUtils.AllocUnmanagedBuf(MAX_STRING_SIZE);
try
{
int size = (int)CWintabFuncs.WTInfoA(
(uint)EWTICategoryIndex.WTI_DEVICES,
(uint)EWTIDevicesIndex.DVC_NAME, buf);
if (size < 1)
{
throw new Exception("GetDeviceInfo returned empty string.");
}
// Strip off final null character before marshalling.
devInfo = CMemUtils.MarshalUnmanagedString(buf, size - 1);
}
catch (Exception ex)
{
MessageBox.Show("FAILED GetDeviceInfo: " + ex.ToString());
}
CMemUtils.FreeUnmanagedBuf(buf);
return devInfo;
}
/// <summary>
/// Returns the default digitizing context, with useful context overrides.
/// </summary>
/// <param name="options_I">caller's options; OR'd into context options</param>
/// <returns>A valid context object or null on error.</returns>
public static CWintabContext GetDefaultDigitizingContext(ECTXOptionValues options_I = 0)
{
// Send all possible data bits (not including extended data).
// This is redundant with CWintabContext initialization, which
// also inits with PK_PKTBITS_ALL.
uint PACKETDATA = (uint)EWintabPacketBit.PK_PKTBITS_ALL; // The Full Monty
uint PACKETMODE = (uint)EWintabPacketBit.PK_BUTTONS;
CWintabContext context = GetDefaultContext(EWTICategoryIndex.WTI_DEFCONTEXT);
if (context != null)
{
// Add digitizer-specific context tweaks.
context.PktMode = 0; // all data in absolute mode (set EWintabPacketBit bit(s) for relative mode)
context.SysMode = false; // system cursor tracks in absolute mode (zero)
// Add caller's options.
context.Options |= (uint)options_I;
// Set the context data bits.
context.PktData = PACKETDATA;
context.PktMode = PACKETMODE;
context.MoveMask = PACKETDATA;
context.BtnUpMask = context.BtnDnMask;
}
return context;
}
/// <summary>
/// Returns the default system context, with useful context overrides.
/// </summary>
/// <param name="options_I">caller's options; OR'd into context options</param>
/// <returns>A valid context object or null on error.</returns>
public static CWintabContext GetDefaultSystemContext(ECTXOptionValues options_I = 0)
{
// Send all possible data bits (not including extended data).
// This is redundant with CWintabContext initialization, which
// also inits with PK_PKTBITS_ALL.
uint PACKETDATA = (uint)EWintabPacketBit.PK_PKTBITS_ALL; // The Full Monty
uint PACKETMODE = (uint)EWintabPacketBit.PK_BUTTONS;
CWintabContext context = GetDefaultContext(EWTICategoryIndex.WTI_DEFSYSCTX);
if (context != null)
{
// TODO: Add system-specific context tweaks.
// Add caller's options.
context.Options |= (uint)options_I;
// Make sure we get data packet messages.
context.Options |= (uint)ECTXOptionValues.CXO_MESSAGES;
// Set the context data bits.
context.PktData = PACKETDATA;
context.PktMode = PACKETMODE;
context.MoveMask = PACKETDATA;
context.BtnUpMask = context.BtnDnMask;
context.Name = "WintabDN Event Data Context";
}
return context;
}
/// <summary>
/// Helper function to get digitizing or system default context.
/// </summary>
/// <param name="contextType_I">Use WTI_DEFCONTEXT for digital context or WTI_DEFSYSCTX for system context</param>
/// <returns>Returns the default context or null on error.</returns>
private static CWintabContext GetDefaultContext(EWTICategoryIndex contextIndex_I)
{
CWintabContext context = new CWintabContext();
IntPtr buf = CMemUtils.AllocUnmanagedBuf(context.LogContext);
try
{
int size = (int)CWintabFuncs.WTInfoA((uint)contextIndex_I, 0, buf);
context.LogContext = CMemUtils.MarshalUnmanagedBuf<WintabLogContext>(buf, size);
}
catch (Exception ex)
{
MessageBox.Show("FAILED GetDefaultContext: " + ex.ToString());
}
CMemUtils.FreeUnmanagedBuf(buf);
return context;
}
/// <summary>
/// Returns the default device. If this value is -1, then it also known as a "virtual device".
/// </summary>
/// <returns></returns>
public static Int32 GetDefaultDeviceIndex()
{
Int32 devIndex = 0;
IntPtr buf = CMemUtils.AllocUnmanagedBuf(devIndex);
try
{
int size = (int)CWintabFuncs.WTInfoA(
(uint)EWTICategoryIndex.WTI_DEFCONTEXT,
(uint)EWTIContextIndex.CTX_DEVICE, buf);
devIndex = CMemUtils.MarshalUnmanagedBuf<Int32>(buf, size);
}
catch (Exception ex)
{
MessageBox.Show("FAILED GetDefaultDeviceIndex: " + ex.ToString());
}
CMemUtils.FreeUnmanagedBuf(buf);
return devIndex;
}
/// <summary>
/// Returns the WintabAxis object for specified device and dimension.
/// </summary>
/// <param name="devIndex_I">Device index (-1 = virtual device)</param>
/// <param name="dim_I">Dimension: AXIS_X, AXIS_Y or AXIS_Z</param>
/// <returns></returns>
public static WintabAxis GetDeviceAxis(Int32 devIndex_I, EAxisDimension dim_I)
{
WintabAxis axis = new WintabAxis();
IntPtr buf = CMemUtils.AllocUnmanagedBuf(axis);
try
{
int size = (int)CWintabFuncs.WTInfoA(
(uint)(EWTICategoryIndex.WTI_DEVICES + devIndex_I),
(uint)dim_I, buf);
// If size == 0, then returns a zeroed struct.
axis = CMemUtils.MarshalUnmanagedBuf<WintabAxis>(buf, size);
}
catch (Exception ex)
{
MessageBox.Show("FAILED GetDeviceAxis: " + ex.ToString());
}
CMemUtils.FreeUnmanagedBuf(buf);
return axis;
}
/// <summary>
/// Returns a 3-element array describing the tablet's rotation range and resolution capabilities
/// </summary>
/// <returns></returns>
public static WintabAxisArray GetDeviceRotation(out bool rotationSupported_O)
{
WintabAxisArray axisArray = new WintabAxisArray();
rotationSupported_O = false;
IntPtr buf = CMemUtils.AllocUnmanagedBuf(axisArray);
try
{
int size = (int)CWintabFuncs.WTInfoA(
(uint)EWTICategoryIndex.WTI_DEVICES,
(uint)EWTIDevicesIndex.DVC_ROTATION, buf);
// If size == 0, then returns a zeroed struct.
axisArray = CMemUtils.MarshalUnmanagedBuf<WintabAxisArray>(buf, size);
rotationSupported_O = (axisArray.array[0].axResolution != 0 && axisArray.array[1].axResolution != 0);
}
catch (Exception ex)
{
MessageBox.Show("FAILED GetDeviceRotation: " + ex.ToString());
}
CMemUtils.FreeUnmanagedBuf(buf);
return axisArray;
}
/// <summary>
/// Returns the number of devices connected (attached).
/// </summary>
/// <returns>tablet count</returns>
public static UInt32 GetNumberOfDevices()
{
UInt32 numDevices = 0;
IntPtr buf = CMemUtils.AllocUnmanagedBuf(numDevices);
try
{
int size = (int)CWintabFuncs.WTInfoA(
(uint)EWTICategoryIndex.WTI_INTERFACE,
(uint)EWTIInterfaceIndex.IFC_NDEVICES, buf);
numDevices = CMemUtils.MarshalUnmanagedBuf<UInt32>(buf, size);
}
catch (Exception ex)
{
MessageBox.Show("FAILED GetNumberOfDevices: " + ex.ToString());
}
CMemUtils.FreeUnmanagedBuf(buf);
return numDevices;
}
/// <summary>
/// Returns whether a stylus is currently connected to the active cursor.
/// </summary>
/// <returns></returns>
public static bool IsStylusActive()
{
bool isStylusActive = false;
IntPtr buf = CMemUtils.AllocUnmanagedBuf(isStylusActive);
try
{
int size = (int)CWintabFuncs.WTInfoA(
(uint)EWTICategoryIndex.WTI_INTERFACE,
(uint)EWTIInterfaceIndex.IFC_NDEVICES, buf);
isStylusActive = CMemUtils.MarshalUnmanagedBuf<bool>(buf, size);
}
catch (Exception ex)
{
MessageBox.Show("FAILED GetNumberOfDevices: " + ex.ToString());
}
CMemUtils.FreeUnmanagedBuf(buf);
return isStylusActive;
}
/// <summary>
/// Returns a string containing the name of the selected stylus.
/// </summary>
/// <param name="index_I">indicates stylus type</param>
/// <returns></returns>
public static string GetStylusName(EWTICursorNameIndex index_I)
{
string stylusName = null;
IntPtr buf = CMemUtils.AllocUnmanagedBuf(MAX_STRING_SIZE);
try
{
int size = (int)CWintabFuncs.WTInfoA(
(uint)index_I,
(uint)EWTICursorsIndex.CSR_NAME, buf);
if (size < 1)
{
throw new Exception("GetStylusName returned empty string.");
}
// Strip off final null character before marshalling.
stylusName = CMemUtils.MarshalUnmanagedString(buf, size - 1);
}
catch (Exception ex)
{
MessageBox.Show("FAILED GetDeviceInfo: " + ex.ToString());
}
CMemUtils.FreeUnmanagedBuf(buf);
return stylusName;
}
/// <summary>
/// Return the WintabAxis object for the specified dimension.
/// </summary>
/// <param name="dimension_I">Dimension to fetch (eg: x, y)</param>
/// <returns></returns>
public static WintabAxis GetTabletAxis(EAxisDimension dimension_I)
{
WintabAxis axis = new WintabAxis();
IntPtr buf = CMemUtils.AllocUnmanagedBuf(axis);
try
{
int size = (int)CWintabFuncs.WTInfoA(
(uint)EWTICategoryIndex.WTI_DEVICES,
(uint)dimension_I, buf);
axis = CMemUtils.MarshalUnmanagedBuf<WintabAxis>(buf, size);
}
catch (Exception ex)
{
MessageBox.Show("FAILED GetMaxPressure: " + ex.ToString());
}
CMemUtils.FreeUnmanagedBuf(buf);
return axis;
}
/// <summary>
/// Return the number of tablets that have at some time been attached.
/// A record of these devices is in the tablet settings. Since there
/// is no direct query for this value, we have to enumerate all of
/// the tablet settings.
/// </summary>
/// <returns>tablet count</returns>
public static UInt32 GetNumberOfConfiguredDevices()
{
UInt32 numConfiguredTablets = 0;
try
{
WintabLogContext ctx = new WintabLogContext();
IntPtr buf = CMemUtils.AllocUnmanagedBuf(ctx);
for (Int32 idx = 0; idx < MAX_NUM_ATTACHED_TABLETS; idx++)
{
int size = (int)CWintabFuncs.WTInfoA(
(UInt32)(EWTICategoryIndex.WTI_DDCTXS + idx), 0, buf);
if (size == 0)
{
break;
}
else
{
numConfiguredTablets++;
}
}
CMemUtils.FreeUnmanagedBuf(buf);
}
catch (Exception ex)
{
MessageBox.Show("FAILED GetNumberOfConfiguredDevices: " + ex.ToString());
}
return numConfiguredTablets;
}
/// <summary>
/// Returns a list of indecies of previous or currently attached devices.
/// It is up to the caller to use the list to determine which devices are
/// actually physically device by responding to data events for those devices.
/// Devices that are not physically attached will, of course, never send
/// a data event.
/// </summary>
/// <returns></returns>
public static List<Byte> GetFoundDevicesIndexList()
{
List<Byte> list = new List<Byte>();
try
{
WintabLogContext ctx = new WintabLogContext();
IntPtr buf = CMemUtils.AllocUnmanagedBuf(ctx);
for (Int32 idx = 0; idx < MAX_NUM_ATTACHED_TABLETS; idx++)
{
int size = (int)CWintabFuncs.WTInfoA(
(UInt32)(EWTICategoryIndex.WTI_DDCTXS + idx), 0, buf);
if (size == 0)
{
break;
}
else
{
list.Add((Byte)idx);
}
}
CMemUtils.FreeUnmanagedBuf(buf);
}
catch (Exception ex)
{
MessageBox.Show("FAILED GetNumberOfConfiguredDevices: " + ex.ToString());
}
return list;
}
}*/
}
}

81
src/Windows/Avalonia.Win32/WintabImpl/WintabMemUtils.cs

@ -0,0 +1,81 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Win32.WintabImpl;
public static class WintabMemUtils
{
/// <summary>
/// Marshal unmanaged data packets into managed WintabPacket data.
/// </summary>
/// <param name="numPkts_I">number of packets to marshal</param>
/// <param name="buf_I">pointer to unmanaged heap memory containing data packets</param>
/// <returns></returns>
/// <summary>
/// Marshal unmanaged data packets into managed WintabPacket data.
/// </summary>
/// <param name="numPkts_I">number of packets to marshal</param>
/// <param name="buf_I">pointer to unmanaged heap memory containing data packets</param>
/// <returns></returns>
public static WintabPacket[] MarshalDataPackets(UInt32 numPkts_I, IntPtr buf_I)
{
if (numPkts_I == 0 || buf_I == IntPtr.Zero)
{
return [];
}
WintabPacket[] packets = new WintabPacket[numPkts_I];
int pktSize = Marshal.SizeOf(new WintabPacket());
for (int pktsIdx = 0; pktsIdx < numPkts_I; pktsIdx++)
{
// Tracing can be added here to capture raw packet data if desired
packets[pktsIdx] = Marshal.PtrToStructure<WintabPacket>(IntPtr.Add(buf_I, pktsIdx * pktSize));
}
return packets;
}
/// <summary>
/// Marshal unmanaged Extension data packets into managed WintabPacketExt data.
/// </summary>
/// <param name="numPkts_I">number of packets to marshal</param>
/// <param name="buf_I">pointer to unmanaged heap memory containing data packets</param>
/// <returns></returns>
public static WintabPacketExt[] MarshalDataExtPackets(UInt32 numPkts_I, IntPtr buf_I)
{
WintabPacketExt[] packets = new WintabPacketExt[numPkts_I];
if (numPkts_I == 0 || buf_I == IntPtr.Zero)
{
return [];
}
// Marshal each WintabPacketExt in the array separately.
// This is "necessary" because none of the other ways I tried to marshal
// seemed to work. It's ugly, but it works.
int pktSize = Marshal.SizeOf(new WintabPacketExt());
Byte[] byteArray = new Byte[numPkts_I * pktSize];
Marshal.Copy(buf_I, byteArray, 0, (int)numPkts_I * pktSize);
Byte[] byteArray2 = new Byte[pktSize];
for (int pktsIdx = 0; pktsIdx < numPkts_I; pktsIdx++)
{
for (int idx = 0; idx < pktSize; idx++)
{
byteArray2[idx] = byteArray[(pktsIdx * pktSize) + idx];
}
IntPtr tmp = Marshal.AllocHGlobal(pktSize);
Marshal.Copy(byteArray2, 0, tmp, pktSize);
packets[pktsIdx] = Marshal.PtrToStructure<WintabPacketExt>(tmp);
}
return packets;
}
}
Loading…
Cancel
Save