diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index bb2bf7f2d2e..8907010a6c7 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1236,6 +1236,15 @@ public struct MOUSEMOVEPOINT [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); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 57542f7f959..610e79feeb7 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/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.Linq; using System.Runtime.CompilerServices; @@ -12,6 +13,7 @@ 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 @@ -32,8 +34,55 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam, 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?>(() => 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); @@ -45,12 +94,31 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam, { 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; } } @@ -166,6 +234,11 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam, Imm32InputMethod.Current.ClearLanguageAndWindow(); } + if (_wintabEnabled) + { + _hCtx.Close(); + } + // Cleanup render targets (_glSurface as IDisposable)?.Dispose(); @@ -289,6 +362,9 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam, { break; } + + if (Math.Abs((int)timestamp - (int)_lastWintabTime) < 50) break; + shouldTakeFocus = ShouldTakeFocusOnClick; if (ShouldIgnoreTouchEmulatedMessage()) { @@ -350,59 +426,66 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam, } 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(), - 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?>(() => CreateIntermediatePoints(currPoint, prevPoint)) + cbSize = Marshal.SizeOf(), + 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?>(() => + CreateIntermediatePoints(currPoint, prevPoint)) + }; + + break; } case WindowsMessage.WM_MOUSEWHEEL: @@ -585,6 +668,8 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam, var args = CreatePointerArgs(device, timestamp, eventType, point, modifiers, info.pointerId); args.IntermediatePoints = CreateLazyIntermediatePoints(info); e = args; + + _nextPointerEventIsInkTest = false; break; } case WindowsMessage.WM_POINTERDEVICEOUTOFRANGE: @@ -1001,6 +1086,36 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam, 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) @@ -1146,6 +1261,61 @@ private unsafe IReadOnlyList CreateIntermediatePoints(MOUSEMOVE } + private IReadOnlyList? 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 @@ -1264,6 +1434,78 @@ private RawPointerPoint CreateRawPointerPoint(POINTER_PEN_INFO info) }; } + 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; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 63336f9d8f7..1fd90857a63 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -27,6 +27,7 @@ using Avalonia.Threading; using static Avalonia.Controls.Win32Properties; using Avalonia.Logging; +using Avalonia.Win32.WintabImpl; namespace Avalonia.Win32 { @@ -109,8 +110,22 @@ internal partial class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindow 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 s_intermediatePointsPooledList = new(); + private static readonly Dictionary s_lastWintabPackets = new(); private static readonly List s_sortedPoints = new(64); private static POINTER_TOUCH_INFO[]? s_historyTouchInfos; private static POINTER_PEN_INFO[]? s_historyPenInfos; @@ -181,10 +196,51 @@ public WindowImpl() _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"); diff --git a/src/Windows/Avalonia.Win32/WintabImpl/WintabContext.cs b/src/Windows/Avalonia.Win32/WintabImpl/WintabContext.cs new file mode 100644 index 00000000000..2bb078e3e6e --- /dev/null +++ b/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 +{ + /// + /// Managed version of AXIS struct. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct WintabAxis + { + /// + /// Specifies the minimum value of the data item in the tablet's na-tive coordinates. + /// + public Int32 axMin; + + /// + /// Specifies the maximum value of the data item in the tablet's na-tive coordinates. + /// + public Int32 axMax; + + /// + /// Indicates the units used in calculating the resolution for the data item. + /// + public UInt32 axUnits; + + /// + /// Is a fixed-point number giving the number of data item incre-ments per physical unit. + /// + public FIX32 axResolution; + } + + /// + /// Array of WintabAxis objects. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct WintabAxisArray + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public WintabAxis[] array; + } + + /// + /// Values to use when asking for X, Y or Z WintabAxis object. + /// + public enum EAxisDimension + { + AXIS_X = EWTIDevicesIndex.DVC_X, + AXIS_Y = EWTIDevicesIndex.DVC_Y, + AXIS_Z = EWTIDevicesIndex.DVC_Z + } + + /// + /// Context option values. + /// + public enum ECTXOptionValues + { + CXO_SYSTEM = 0x0001, + CXO_PEN = 0x0002, + CXO_MESSAGES = 0x0004, + CXO_CSRMESSAGES = 0x0008, + CXO_MGNINSIDE = 0x4000, + CXO_MARGIN = 0x8000, + } + + /// + /// Context status values. + /// + public enum ECTXStatusValues + { + CXS_DISABLED = 0x0001, + CXS_OBSCURED = 0x0002, + CXS_ONTOP = 0x0004 + } + + /// + /// Context lock values. + /// + public enum ECTXLockValues + { + CXL_INSIZE = 0x0001, + CXL_INASPECT = 0x0002, + CXL_SENSITIVITY = 0x0004, + CXL_MARGIN = 0x0008, + CXL_SYSOUT = 0x0010 + } + + /// + /// 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. + /// + [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; + } + + /// + /// Class to support access to Wintab context management. + /// + 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(); + IntPtr buf = Marshal.AllocHGlobal(sizeOfLogContext); + + int size = (int)WintabFuncs.WTInfoA((uint)contextIndex_I, 0, buf); + + context.LogContext = Marshal.PtrToStructure(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; + } + + /// + /// Default constructor sets all data bits to be captured. + /// + 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; + } + + /// + /// Open a Wintab context to the specified hwnd. + /// + /// parent window for the context + /// true to enable, false to disable + /// Returns non-zero context handle if successful. + 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; + } + + /// + /// Open a Wintab context that will send packet events to a message window. + /// + /// Returns true if successful. + public bool Open(IntPtr hwnd) + { + m_hCTX = new HCTX((IntPtr)WintabFuncs.WTOpenA(hwnd, ref m_logContext, true)); + return m_hCTX.Value != IntPtr.Zero; + } + + /// + /// Close the context for this object. + /// + /// true if context successfully closed + 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; + } + + /// + /// Enable/disable this Wintab context. + /// + /// true = enable + /// Returns true if completed successfully + 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; + } + + /// + /// Sends a tablet context to the top or bottom of the order of overlapping tablet contexts + /// + /// true = send tablet to top of order + /// Returns true if successsful + 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; + } + + /// + /// Logical Wintab context managed by this object. + /// + public WintabLogContext LogContext { get { return m_logContext; } set { m_logContext = value; } } + + /// + /// Handle (identifier) used to identify this context. + /// + public HCTX HCtx { get { return m_hCTX; } } + + /// + /// Get/Set context name. + /// + public string Name { get { return m_logContext.lcName; } set { m_logContext.lcName = value; } } + + /// + /// 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. + /// + public UInt32 Options { get { return m_logContext.lcOptions; } set { m_logContext.lcOptions = value; } } + + /// + /// 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. + /// + public UInt32 Status { get { return m_logContext.lcStatus; } set { m_logContext.lcStatus = value; } } + + /// + /// 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. + /// + public UInt32 Locks { get { return m_logContext.lcLocks; } set { m_logContext.lcLocks = value; } } + + /// + /// Specifies the range of message numbers that will be used for + /// reporting the activity of the context. + /// + public UInt32 MsgBase { get { return m_logContext.lcMsgBase; } set { m_logContext.lcMsgBase = value; } } + + /// + /// Specifies the device whose input the context processes. + /// + public UInt32 Device { get { return m_logContext.lcDevice; } set { m_logContext.lcDevice = value; } } + + /// + /// Specifies the desired packet report rate in Hertz. Once the con-text is opened, this field will + /// contain the actual report rate. + /// + public UInt32 PktRate { get { return m_logContext.lcPktRate; } set { m_logContext.lcPktRate = value; } } + + /// + /// Specifies which optional data items will be in packets returned from the context. Requesting + /// unsupported data items will cause Open() to fail. + /// + public WTPKT PktData { get { return m_logContext.lcPktData; } set { m_logContext.lcPktData = value; } } + + /// + /// 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. + /// + public WTPKT PktMode { get { return m_logContext.lcPktMode; } set { m_logContext.lcPktMode = value; } } + + /// + /// 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. + /// + public WTPKT MoveMask { get { return m_logContext.lcMoveMask; } set { m_logContext.lcMoveMask = value; } } + + /// + /// 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. + /// + public UInt32 BtnDnMask { get { return m_logContext.lcBtnDnMask; } set { m_logContext.lcBtnDnMask = value; } } + + /// + /// 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. + /// + public UInt32 BtnUpMask { get { return m_logContext.lcBtnUpMask; } set { m_logContext.lcBtnUpMask = value; } } + + /// + /// 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. + /// + public Int32 InOrgX { get { return m_logContext.lcInOrgX; } set { m_logContext.lcInOrgX = value; } } + + /// + /// 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. + /// + public Int32 InOrgY { get { return m_logContext.lcInOrgY; } set { m_logContext.lcInOrgY = value; } } + + /// + /// 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. + /// + public Int32 InOrgZ { get { return m_logContext.lcInOrgZ; } set { m_logContext.lcInOrgZ = value; } } + + /// + /// 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. + /// + public Int32 InExtX { get { return m_logContext.lcInExtX; } set { m_logContext.lcInExtX = value; } } + + /// + /// 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. + /// + public Int32 InExtY { get { return m_logContext.lcInExtY; } set { m_logContext.lcInExtY = value; } } + + /// + /// 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. + /// + public Int32 InExtZ { get { return m_logContext.lcInExtZ; } set { m_logContext.lcInExtZ = value; } } + + /// + /// Specifies the X origin of the context's output area in context output coordinates. Value is used in + /// coordinate scaling for absolute mode only. + /// + public Int32 OutOrgX { get { return m_logContext.lcOutOrgX; } set { m_logContext.lcOutOrgX = value; } } + + /// + /// Specifies the Y origin of the context's output area in context output coordinates. Value is used in + /// coordinate scaling for absolute mode only. + /// + public Int32 OutOrgY { get { return m_logContext.lcOutOrgY; } set { m_logContext.lcOutOrgY = value; } } + + /// + /// Specifies the Z origin of the context's output area in context output coordinates. Value is used in + /// coordinate scaling for absolute mode only. + /// + public Int32 OutOrgZ { get { return m_logContext.lcOutOrgZ; } set { m_logContext.lcOutOrgZ = value; } } + + /// + /// Specifies the X extent of the context's output area in context output coordinates. Value is used + /// in coordinate scaling for absolute mode only. + /// + public Int32 OutExtX { get { return m_logContext.lcOutExtX; } set { m_logContext.lcOutExtX = value; } } + + /// + /// Specifies the Y extent of the context's output area in context output coordinates. Value is used + /// in coordinate scaling for absolute mode only. + /// + public Int32 OutExtY { get { return m_logContext.lcOutExtY; } set { m_logContext.lcOutExtY = value; } } + + /// + /// Specifies the Z extent of the context's output area in context output coordinates. Value is used + /// in coordinate scaling for absolute mode only. + /// + public Int32 OutExtZ { get { return m_logContext.lcOutExtZ; } set { m_logContext.lcOutExtZ = value; } } + + /// + /// Specifies the relative-mode sensitivity factor for the x axis. + /// + public FIX32 SensX { get { return m_logContext.lcSensX; } set { m_logContext.lcSensX = value; } } + + /// + /// Specifies the relative-mode sensitivity factor for the y axis. + /// + public FIX32 SensY { get { return m_logContext.lcSensY; } set { m_logContext.lcSensY = value; } } + + /// + /// Specifies the relative-mode sensitivity factor for the Z axis. + /// + public FIX32 SensZ { get { return m_logContext.lcSensZ; } set { m_logContext.lcSensZ = value; } } + + /// + /// Specifies the system cursor tracking mode. Zero specifies absolute; non-zero means relative. + /// + public bool SysMode { get { return m_logContext.lcSysMode; } set { m_logContext.lcSysMode = value; } } + + /// + /// Specifies the X origin of the screen mapping area for system cursor tracking, in screen coordinates. + /// + public Int32 SysOrgX { get { return m_logContext.lcSysOrgX; } set { m_logContext.lcSysOrgX = value; } } + + /// + /// Specifies the Y origin of the screen mapping area for system cursor tracking, in screen coordinates. + /// + public Int32 SysOrgY { get { return m_logContext.lcSysOrgY; } set { m_logContext.lcSysOrgY = value; } } + + /// + /// Specifies the X extent of the screen mapping area for system cursor tracking, in screen coordinates. + /// + public Int32 SysExtX { get { return m_logContext.lcSysExtX; } set { m_logContext.lcSysExtX = value; } } + + /// + /// Specifies the Y extent of the screen mapping area for system cursor tracking, in screen coordinates. + /// + public Int32 SysExtY { get { return m_logContext.lcSysExtY; } set { m_logContext.lcSysExtY = value; } } + + /// + /// Specifies the system-cursor relative-mode sensitivity factor for the x axis. + /// + public FIX32 SysSensX { get { return m_logContext.lcSysSensX; } set { m_logContext.lcSysSensX = value; } } + + /// + /// Specifies the system-cursor relative-mode sensitivity factor for the y axis. + /// + public FIX32 SysSensY { get { return m_logContext.lcSysSensY; } set { m_logContext.lcSysSensY = value; } } + } +} diff --git a/src/Windows/Avalonia.Win32/WintabImpl/WintabData.cs b/src/Windows/Avalonia.Win32/WintabImpl/WintabData.cs new file mode 100644 index 00000000000..ee65b3f4e1a --- /dev/null +++ b/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 +{ + /// + /// Wintab Packet bits. + /// + 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 + } + + /// + /// Wintab event messsages sent to an application. + /// See Wintab Spec 1.4 for a description of these messages. + /// + 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 + } + + /// + /// Wintab packet status values. + /// + public enum EWintabPacketStatusValue + { + /// + /// Specifies that the cursor is out of the context. + /// + TPS_PROXIMITY = 0x0001, + + /// + /// Specifies that the event queue for the context has overflowed. + /// + TPS_QUEUE_ERR = 0x0002, + + /// + /// Specifies that the cursor is in the margin of the context. + /// + TPS_MARGIN = 0x0004, + + /// + /// Specifies that the cursor is out of the context, but that the + /// context has grabbed input while waiting for a button release event. + /// + TPS_GRAB = 0x0008, + + /// + /// Specifies that the cursor is in its inverted state. + /// + TPS_INVERT = 0x0010 + } + + /// + /// WintabPacket.pkButton codes. + /// + public enum EWintabPacketButtonCode + { + /// + /// No change in button state. + /// + TBN_NONE = 0, + + /// + /// Button was released. + /// + TBN_UP = 1, + + /// + /// Button was pressed. + /// + TBN_DOWN = 2 + } + + /// + /// Pen Orientation + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct WTOrientation + { + /// + /// Specifies the clockwise rotation of the cursor about the + /// z axis through a full circular range. + /// + public Int32 orAzimuth; + + /// + /// 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. + /// + public Int32 orAltitude; + + /// + /// Specifies the clockwise rotation of the cursor about its own major axis. + /// + public Int32 orTwist; + } + + /// + /// Pen Rotation + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct WTRotation + { + /// + /// Specifies the pitch of the cursor. + /// + public Int32 rotPitch; + + /// + /// Specifies the roll of the cursor. + /// + public Int32 rotRoll; + + /// + /// Specifies the yaw of the cursor. + /// + public Int32 rotYaw; + } + + /// + /// Wintab data packet. Contains the "Full Monty" for all possible data values. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct WintabPacket + { + /// + /// Specifies the context that generated the event. + /// + public HCTX pkContext; // PK_CONTEXT + + /// + /// 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. + /// + public UInt32 pkStatus; // PK_STATUS + + /// + /// 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. + /// + public UInt32 pkTime; // PK_TIME + + /// + /// Specifies which of the included packet data items have changed since the + /// previously posted event. + /// + public WTPKT pkChanged; // PK_CHANGED + + /// + /// This is an identifier assigned to the packet by the context. Consecutive + /// packets will have consecutive serial numbers. + /// + public UInt32 pkSerialNumber; // PK_SERIAL_NUMBER + + /// + /// Specifies which cursor type generated the packet. + /// + public UInt32 pkCursor; // PK_CURSOR + + /// + /// 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. + /// + public UInt32 pkButtons; // PK_BUTTONS + + /// + /// 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. + /// + public Int32 pkX; // PK_X + + /// + /// 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. + /// + public Int32 pkY; // PK_Y + + /// + /// 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. + /// + public Int32 pkZ; // PK_Z + + /// + /// 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. + /// + public UInt32 pkNormalPressure; // PK_NORMAL_PRESSURE + + /// + /// 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. + /// + public UInt32 pkTangentPressure; // PK_TANGENT_PRESSURE + + /// + /// Contains updated cursor orientation information. See the + /// WTOrientation structure for details. + /// + public WTOrientation pkOrientation; // ORIENTATION + + public static int ByteSize { get; } = Marshal.SizeOf(); + } + + /// + /// Common properties for control extension data transactions. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct WTExtensionBase + { + /// + /// Specifies the Wintab context to which these properties apply. + /// + public HCTX nContext; + + /// + /// Status of setting/getting properties. + /// + public UInt32 nStatus; + + /// + /// Timestamp applied to property transaction. + /// + public WTPKT nTime; + + /// + /// Reserved - not used. + /// + public UInt32 nSerialNumber; + } + + /// + /// Extension data for one Express Key. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct WTExpKeyData + { + /// + /// Tablet index where control is found. + /// + public byte nTablet; + + /// + /// Zero-based control index. + /// + public byte nControl; + + /// + /// Zero-based index indicating side of tablet where control found (0 = left, 1 = right). + /// + public byte nLocation; + + /// + /// Reserved - not used + /// + public byte nReserved; + + /// + /// Indicates Express Key button press (1 = pressed, 0 = released) + /// + public WTPKT nState; + } + + /// + /// Extension data for one touch ring or one touch strip. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct WTSliderData + { + /// + /// Tablet index where control is found. + /// + public byte nTablet; + + /// + /// Zero-based control index. + /// + public byte nControl; + + /// + /// Zero-based current active mode of control. + /// This is the mode selected by control's toggle button. + /// + public byte nMode; + + /// + /// Reserved - not used + /// + public byte nReserved; + + /// + /// 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. + /// + public WTPKT nPosition; + } + + /// + /// 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. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct WintabPacketExt + { + /// + /// Extension control properties common to all control types. + /// + public WTExtensionBase pkBase; + + /// + /// Extension data for one Express Key. + /// + public WTExpKeyData pkExpKey; + + /// + /// Extension data for one Touch Strip. + /// + public WTSliderData pkTouchStrip; + + /// + /// Extension data for one Touch Ring. + /// + public WTSliderData pkTouchRing; + } + + /// + /// Class to support capture and management of Wintab daa. + /// + public class WintabData + { + private WintabContext m_context; + + /// + /// CWintabData constructor + /// + /// logical context for this data object + public WintabData(WintabContext context_I) + { + m_context = context_I; + } + + /* + /// + /// Removes the WTPacket handler so that messages are ignored. + /// + /// WT_PACKET event handler supplied by the client. + public void RemoveWTPacketEventHandler(EventHandler 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()); + } + } + + /// + /// Set the handler to be called when WT_CTX* messages are received. + /// + /// WT_CTX* event handler supplied by the client. + public void SetStatusEventHandler(EventHandler 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()); + } + } + + /// + /// Removes the WT_CTX* handler so that messages are ignored. + /// + /// WT_CTX* event handler supplied by the client. + public void RemoveStatusEventHandler(EventHandler 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()); + } + } + + /// + /// Set the handler to be called when WT_INFOCHANGE messages are received. + /// + /// WT_INFOCHANGE event handler supplied by the client. + public void SetInfoChangeEventHandler(EventHandler handler_I) + { + try + { + MessageEvents.InfoChgMessageReceived += handler_I; + MessageEvents.WatchMessage(EWintabEventMessage.WT_INFOCHANGE); + } + catch (Exception ex) + { + MessageBox.Show("FAILED CWintabData.SetWTInfoChangeEventHandler: " + ex.ToString()); + } + } + + /// + /// Removes the WT_INFOCHANGE handler so that messages are ignored. + /// + /// WT_INFOCHANGE event handler supplied by the client. + public void RemoveInfoChangeEventHandler(EventHandler handler_I) + { + try + { + MessageEvents.UnWatchMessage(EWintabEventMessage.WT_INFOCHANGE); + MessageEvents.InfoChgMessageReceived -= handler_I; + } + catch (Exception ex) + { + MessageBox.Show("FAILED CWintabData.RemoveWTInfoChangeEventHandler: " + ex.ToString()); + } + } + */ + + /// + /// Set packet queue size for this data object's context. + /// + /// desired #packets in queue + /// Returns true if operation successful + public bool SetPacketQueueSize(UInt32 numPkts_I) + { + bool status = false; + + + CheckForValidHCTX("SetPacketQueueSize"); + status = WintabFuncs.WTQueueSizeSet(m_context.HCtx, numPkts_I); + + return status; + } + + /// + /// Get packet queue size for this data object's context. + /// + /// Returns a packet queue size in #packets or 0 if fails + 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); + } + + /// + /// Returns one packet of WintabPacketExt data from the packet queue. + /// + /// Wintab context to be used when asking for the data + /// Identifier for the tablet event packet to return. + /// Returns a data packet with non-null context if successful. + 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]; + } + + /// + /// Returns one packet of WintabPacket data from the packet queue. (Deprecated) + /// + /// Identifier for the tablet event packet to return. + /// Returns a data packet with non-null context if successful. + public WintabPacket GetDataPacket(UInt32 pktID_I) + { + return GetDataPacket(m_context.HCtx, pktID_I); + } + + /// + /// Returns one packet of Wintab data from the packet queue. + /// + /// Wintab context to be used when asking for the data + /// Identifier for the tablet event packet to return. + /// Returns a data packet with non-null context if successful. + public WintabPacket GetDataPacket(IntPtr hCtx, UInt32 pktID_I) + { + IntPtr buf = Marshal.AllocHGlobal(Marshal.SizeOf()); + 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(buf); + } + else + { + // + // If fails, make sure context is zero. + // + packet.pkContext = new HCTX(IntPtr.Zero); + } + + Marshal.FreeHGlobal(buf); + + return packet; + } + + /// + /// Removes all pending data packets from the context's queue. + /// + public void FlushDataPackets(uint numPacketsToFlush_I) + { + CheckForValidHCTX("FlushDataPackets"); + WintabFuncs.WTPacketsGet(m_context.HCtx, numPacketsToFlush_I, IntPtr.Zero); + } + + /// + /// Returns an array of Wintab data packets from the packet queue. + /// + /// Specifies the maximum number of packets to return. + /// If true, returns data packets and removes them from the queue. + /// Number of packets actually returned. + /// 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). + 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; + } + + /// + /// Throws exception if logical context for this data object is zero. + /// + private void CheckForValidHCTX(string msg) + { + if (m_context.HCtx == 0) + { + throw new Exception(msg + " - Bad Context"); + } + } + } +} diff --git a/src/Windows/Avalonia.Win32/WintabImpl/WintabFuncs.cs b/src/Windows/Avalonia.Win32/WintabImpl/WintabFuncs.cs new file mode 100644 index 00000000000..fd606b3b525 --- /dev/null +++ b/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. + + /// + /// Managed implementation of Wintab HWND typedef. + /// Holds native Window handle. + /// + [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; + } + } + + /// + /// Managed implementation of Wintab WTPKT typedef. + /// Holds Wintab packet identifier. + /// + [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(); + } + } + + /// + /// Managed implementation of Wintab FIX32 typedef. + /// Used for a fixed-point arithmetic value. + /// + [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(); + } + } + + /// + /// Managed implementation of Wintab HCTX typedef. + /// Holds a Wintab context identifier. + /// + [StructLayout(LayoutKind.Sequential)] + public readonly struct HCTX : IEquatable + { + 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(); + } + } + + /// + /// Index values for WTInfo wCategory parameter. + /// + 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 + } + + /// + /// Index values for WTI_INTERFACE. + /// + 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 + } + + /// + /// Index values for WTI_DEVICES + /// + 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 + } + + /// + /// Index values for WTI_CURSORS. + /// + 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 + } + + /// + /// Index used with CSR_NAME to get stylus types. + /// + 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 + } + + /// + /// Index values for WTI contexts. + /// + 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 + } + + /// + /// P/Invoke wrappers for Wintab functions. + /// See Wintab_v140.doc (Wintab 1.4 spec) and related Wintab documentation for details. + /// + public class WintabFuncs + { + /// + /// 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. + /// + /// Identifies the category from which information is being requested. + /// Identifies which information is being requested from within the category. + /// Points to a buffer to hold the requested information. + /// 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. + /// + [DllImport("Wintab32.dll", CharSet = CharSet.Auto)] + public static extern UInt32 WTInfoA(UInt32 wCategory_I, UInt32 nIndex_I, IntPtr lpOutput_O); + + /// + /// 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. + /// + /// Identifies the window that owns the tablet context, and receives messages from the context. + /// Points to an application-provided WintabLogContext data structure describing the context to be opened. + /// Specifies whether the new context will immediately begin processing input data. + /// The return value identifies the new context. It is NULL if the context is not opened. + [DllImport("Wintab32.dll", CharSet = CharSet.Auto)] + public static extern P_HCTX WTOpenA(P_HWND hWnd_I, ref WintabLogContext logContext_I, bool enable_I); + + /// + /// This function closes and destroys the tablet context object. + /// + /// Identifies the context to be closed. + /// The function returns a non-zero value if the context was valid and was destroyed. Otherwise, it returns zero. + [DllImport("Wintab32.dll", CharSet = CharSet.Auto)] + public static extern bool WTClose(P_HCTX hctx_I); + + /// + /// This function enables or disables a tablet context, temporarily turning on or off the processing of packets. + /// + /// Identifies the context to be enabled or disabled. + /// Specifies enabling if non-zero, disabling if zero. + /// The function returns true if the enable or disable request was satisfied. + [DllImport("Wintab32.dll", CharSet = CharSet.Auto)] + public static extern bool WTEnable(P_HCTX hctx_I, bool enable_I); + + /// + /// This function sends a tablet context to the top or bottom of the order of overlapping tablet contexts. + /// + /// Identifies the context to move within the overlap order. + /// Specifies sending the context to the top of the overlap order true, or to the bottom if false. + /// The function returns true if successful. + [DllImport("Wintab32.dll", CharSet = CharSet.Auto)] + public static extern bool WTOverlap(P_HCTX hctx_I, bool toTop_I); + + /// + /// This function returns the number of packets the context's queue can hold. + /// + /// Identifies the context whose queue size is being returned. + /// The number of packets the queue can hold. + [DllImport("Wintab32.dll", CharSet = CharSet.Auto)] + public static extern UInt32 WTQueueSizeGet(P_HCTX hctx_I); + + /// + /// This function attempts to change the context's queue size to the value specified in nPkts_I. + /// + /// Identifies the context whose queue size is being set. + /// Specifies the requested queue size. + /// The return value is true if the queue size was successfully changed. + [DllImport("Wintab32.dll", CharSet = CharSet.Auto)] + public static extern bool WTQueueSizeSet(P_HCTX hctx_I, UInt32 nPkts_I); + + /// + /// 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. + /// + /// Identifies the context whose packets are being returned. + /// Serial number of the tablet event to return. + /// Buffer to receive the event packet. + /// 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. + [DllImport("Wintab32.dll", CharSet = CharSet.Auto)] + public static extern bool WTPacket(P_HCTX hctx_I, UInt32 pktSerialNum_I, IntPtr pktBuf_O); + + /// + /// 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 + /// + /// Identifies the context whose packets are being returned. + /// Specifies the maximum number of packets to return + /// Buffer to receive the event packets. + /// The return value is the number of packets copied in the buffer. + [DllImport("Wintab32.dll", CharSet = CharSet.Auto)] + public static extern UInt32 WTPacketsGet(P_HCTX hctx_I, UInt32 maxPkts_I, IntPtr pktBuf_O); + + /// 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. + /// + /// Identifies the context whose packets are being returned. + /// Identifier of the oldest tablet event to return. + /// Identifier of the newest tablet event to return. + /// Specifies the maximum number of packets to return. + /// Buffer to receive the event packets. + /// Number of packets actually copied. + /// The return value is the total number of packets found in the queue + /// between pktIDStart_I and pktIDEnd_I. + [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); + + /// + /// 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. + /// + /// Identifies the context whose packets are being read. + /// Identifier of the oldest tablet event to return. + /// Identifier of the newest tablet event to return. + /// Specifies the maximum number of packets to return. + /// Buffer to receive the event packets. + /// Number of packets actually copied. + /// The return value is the total number of packets found in the queue between + /// pktIDStart_I and pktIDEnd_I. + [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); + + /// + /// This function returns the identifiers of the oldest and newest packets currently in the queue. + /// + /// Identifies the context whose queue is being queried. + /// Identifier of the oldest packet in the queue. + /// Identifier of the newest packet in the queue. + /// This function returns bool if successful. + [DllImport("Wintab32.dll", CharSet = CharSet.Auto)] + public static extern bool WTQueuePacketsEx(P_HCTX hctx_I, ref UInt32 pktIDOldest_O, ref UInt32 pktIDNewest_O); + + /// + /// This function retrieves any context-specific data for an extension. + /// + /// Identifies the context whose extension attributes are being retrieved. + /// Identifies the extension tag for which context-specific data is being retrieved. + /// Points to a buffer to hold retrieved data (WTExtensionProperty). + /// + [DllImport("Wintab32.dll", CharSet = CharSet.Auto)] + public static extern bool WTExtGet(P_HCTX hctx_I, UInt32 extTag_I, IntPtr extData_O); + + /// + /// This function sets any context-specific data for an extension. + /// + /// Identifies the context whose extension attributes are being modified. + /// Identifies the extension tag for which context-specific data is being modified. + /// Points to the new data (WTExtensionProperty). + /// + [DllImport("Wintab32.dll", CharSet = CharSet.Auto)] + public static extern bool WTExtSet(P_HCTX hctx_I, UInt32 extTag_I, IntPtr extData_I); + } +} diff --git a/src/Windows/Avalonia.Win32/WintabImpl/WintabInfo.cs b/src/Windows/Avalonia.Win32/WintabImpl/WintabInfo.cs new file mode 100644 index 00000000000..19bd03cf6ab --- /dev/null +++ b/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 +{ + /// + /// Class to access Wintab interface data. + /// + 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; + + /// + /// Returns TRUE if Wintab service is running and responsive. + /// + /// + public static bool IsWintabAvailable() + { + try + { + IntPtr buf = IntPtr.Zero; + + var status = (WintabFuncs.WTInfoA(0, 0, buf) > 0); + + return status; + } + catch + { + return false; + } + } + + /// + /// Return max normal pressure supported by tablet. + /// + /// TRUE=> normal pressure; + /// FALSE=> tangential pressure (not supported on all tablets) + /// maximum pressure value or zero on error + 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(buf); + + Marshal.FreeHGlobal(buf); + + return pressureAxis.axMax; + } + + /// + /// Returns a 3-element array describing the tablet's orientation range and resolution capabilities. + /// + /// + 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(buf); + tiltSupported_O = (axisArray.array[0].axResolution != 0 && axisArray.array[1].axResolution != 0); + + Marshal.FreeHGlobal(buf); + + return axisArray; + } + + /*/// + /// Returns a string containing device name. + /// + /// + 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; + } + + /// + /// Returns the default digitizing context, with useful context overrides. + /// + /// caller's options; OR'd into context options + /// A valid context object or null on error. + 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; + } + + /// + /// Returns the default system context, with useful context overrides. + /// + /// caller's options; OR'd into context options + /// A valid context object or null on error. + 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; + } + + /// + /// Helper function to get digitizing or system default context. + /// + /// Use WTI_DEFCONTEXT for digital context or WTI_DEFSYSCTX for system context + /// Returns the default context or null on error. + 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(buf, size); + } + catch (Exception ex) + { + MessageBox.Show("FAILED GetDefaultContext: " + ex.ToString()); + } + + CMemUtils.FreeUnmanagedBuf(buf); + + return context; + } + + /// + /// Returns the default device. If this value is -1, then it also known as a "virtual device". + /// + /// + 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(buf, size); + } + catch (Exception ex) + { + MessageBox.Show("FAILED GetDefaultDeviceIndex: " + ex.ToString()); + } + + CMemUtils.FreeUnmanagedBuf(buf); + + return devIndex; + } + + /// + /// Returns the WintabAxis object for specified device and dimension. + /// + /// Device index (-1 = virtual device) + /// Dimension: AXIS_X, AXIS_Y or AXIS_Z + /// + 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(buf, size); + } + catch (Exception ex) + { + MessageBox.Show("FAILED GetDeviceAxis: " + ex.ToString()); + } + + CMemUtils.FreeUnmanagedBuf(buf); + + return axis; + } + + /// + /// Returns a 3-element array describing the tablet's rotation range and resolution capabilities + /// + /// + 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(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; + } + + /// + /// Returns the number of devices connected (attached). + /// + /// tablet count + 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(buf, size); + } + catch (Exception ex) + { + MessageBox.Show("FAILED GetNumberOfDevices: " + ex.ToString()); + } + + CMemUtils.FreeUnmanagedBuf(buf); + + return numDevices; + } + + /// + /// Returns whether a stylus is currently connected to the active cursor. + /// + /// + 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(buf, size); + } + catch (Exception ex) + { + MessageBox.Show("FAILED GetNumberOfDevices: " + ex.ToString()); + } + + CMemUtils.FreeUnmanagedBuf(buf); + + return isStylusActive; + } + + /// + /// Returns a string containing the name of the selected stylus. + /// + /// indicates stylus type + /// + 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; + } + + /// + /// Return the WintabAxis object for the specified dimension. + /// + /// Dimension to fetch (eg: x, y) + /// + 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(buf, size); + } + catch (Exception ex) + { + MessageBox.Show("FAILED GetMaxPressure: " + ex.ToString()); + } + + CMemUtils.FreeUnmanagedBuf(buf); + + return axis; + } + + /// + /// 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. + /// + /// tablet count + 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; + } + + /// + /// 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. + /// + /// + public static List GetFoundDevicesIndexList() + { + List list = new List(); + + 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; + } + }*/ + } +} diff --git a/src/Windows/Avalonia.Win32/WintabImpl/WintabMemUtils.cs b/src/Windows/Avalonia.Win32/WintabImpl/WintabMemUtils.cs new file mode 100644 index 00000000000..16750ceb5ec --- /dev/null +++ b/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 +{ + /// + /// Marshal unmanaged data packets into managed WintabPacket data. + /// + /// number of packets to marshal + /// pointer to unmanaged heap memory containing data packets + /// + + /// + /// Marshal unmanaged data packets into managed WintabPacket data. + /// + /// number of packets to marshal + /// pointer to unmanaged heap memory containing data packets + /// + 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(IntPtr.Add(buf_I, pktsIdx * pktSize)); + } + + return packets; + } + + /// + /// Marshal unmanaged Extension data packets into managed WintabPacketExt data. + /// + /// number of packets to marshal + /// pointer to unmanaged heap memory containing data packets + /// + 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(tmp); + } + + return packets; + } +}