using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Threading;
using System.Windows.Forms;
using DevCase.Win32;
using DevCase.Win32.Enums;
namespace DevCase {
namespace Core.IO.Devices.Input {
/// <summary>
/// Provides mouse interaction related utilities.
/// </summary>
internal sealed class UtilMouse {
/// <summary>
/// Prevents a default instance of the <see cref="UtilMouse"/> class from being created.
/// </summary>
[DebuggerNonUserCode]
private UtilMouse() { }
/// <summary>
/// Defines a button message to send to a window.
/// </summary>
internal enum MouseButtonMessage : int {
/// <summary>
/// Hold down the left mouse button.
/// </summary>
LeftDown = WindowMessages.WM_LButtonDown,
/// <summary>
/// Release up the left mouse button.
/// </summary>
LeftUp = WindowMessages.WM_LButtonUp,
/// <summary>
/// Hold down the right mouse button.
/// </summary>
RightDown = WindowMessages.WM_RButtonDown,
/// <summary>
/// Release up the right mouse button.
/// </summary>
RightUp = WindowMessages.WM_RButtonUp,
/// <summary>
/// Hold down the middle mouse button.
/// </summary>
MiddleDown = WindowMessages.WM_MButtonDown,
/// <summary>
/// Release up the middle mouse button.
/// </summary>
MiddleUp = WindowMessages.WM_MButtonUp,
/// <summary>
/// Move down the mouse wheel.
/// </summary>
WheelDown,
/// <summary>
/// Move up the mouse wheel.
/// </summary>
WheelUp,
/// <summary>
/// Tilt horizontal mouse wheel to left.
/// </summary>
WheelLeft,
/// <summary>
/// Tilt horizontal mouse wheel to right.
/// </summary>
WheelRight
}
/// <summary>
/// Defines a click of a mouse button.
/// </summary>
internal enum MouseButtonClick : int {
/// <summary>
/// Left mouse button click.
/// </summary>
LeftButton,
/// <summary>
/// Right mouse button click.
/// </summary>
RightButton,
/// <summary>
/// Middle mouse button click.
/// </summary>
MiddleButton
}
/// <summary>
/// Sends a mouse button message to the specified window,
/// on the specified coordinates relative to its client-area,
/// by calling <see cref="Win32.NativeMethods.SendMessage"/> function.
/// </summary>
///
/// <param name="hWnd">
/// A handle to the target window.
/// </param>
///
/// <param name="button">
/// The mouse button message to send.
/// </param>
///
/// <param name="pt">
/// The coordinates within the client area of the window,
/// where to send the mouse button message.
/// </param>
///
/// <returns>
/// <see langword="True"/> if the function succeeds; otherwise, <see langword="False"/>.
/// </returns>
[DebuggerStepThrough]
internal static bool SendMouseButtonMessage(IntPtr hWnd, MouseButtonMessage button, Point pt) {
return UtilMouse.SendMouseButtonMessage_Internal(hWnd, button, pt, async: false);
}
/// <summary>
/// Asynchronouslly sends a mouse button message to the specified window,
/// on the specified coordinates relative to its client-area,
/// by calling <see cref="Win32.NativeMethods.PostMessage"/> function.
/// </summary>
///
/// <param name="hWnd">
/// A handle to the target window.
/// </param>
///
/// <param name="button">
/// The mouse button message to send.
/// </param>
///
/// <param name="pt">
/// The coordinates within the client area of the window,
/// where to send the mouse button message.
/// </param>
///
/// <returns>
/// <see langword="True"/> if the function succeeds; otherwise, <see langword="False"/>.
/// </returns>
[DebuggerStepThrough]
internal static bool SendMouseButtonMessageAsync(IntPtr hWnd, MouseButtonMessage button, Point pt) {
return UtilMouse.SendMouseButtonMessage_Internal(hWnd, button, pt, async: true);
}
/// <summary>
/// Sends a mouse button click to the specified window,
/// on the specified coordinates relative to its client-area,
/// by calling <see cref="Win32.NativeMethods.SendMessage"/> function.
/// </summary>
///
/// <param name="hWnd">
/// A handle to the target window.
/// </param>
///
/// <param name="button">
/// The mouse button click to send.
/// </param>
///
/// <param name="pt">
/// The coordinates within the client area of the window,
/// where to send the mouse button click.
/// </param>
///
/// <returns>
/// <see langword="True"/> if the function succeeds; otherwise, <see langword="False"/>.
/// </returns>
[DebuggerStepThrough]
internal static bool SendMouseButtonClick(IntPtr hWnd, MouseButtonClick button, Point pt) {
return UtilMouse.SendMouseButtonClick_Internal(hWnd, button, pt, async: false);
}
/// <summary>
/// Asynchronouslly sends a mouse button click to the specified window,
/// on the specified coordinates relative to its client-area,
/// by calling <see cref="Win32.NativeMethods.PostMessage"/> function.
/// </summary>
///
/// <param name="hWnd">
/// A handle to the target window.
/// </param>
///
/// <param name="button">
/// The mouse button click to send.
/// </param>
///
/// <param name="pt">
/// The coordinates within the client area of the window,
/// where to send the mouse button click.
/// </param>
///
/// <returns>
/// <see langword="True"/> if the function succeeds; otherwise, <see langword="False"/>.
/// </returns>
[DebuggerStepThrough]
internal static bool SendMouseButtonClickAsync(IntPtr hWnd, MouseButtonClick button, Point pt) {
return UtilMouse.SendMouseButtonClick_Internal(hWnd, button, pt, async: true);
}
/// <summary>
/// *** FOR INTERNAL USE ONLY ***
/// <para></para>
/// Sends a mouse button message to the specified window,
/// on the specified coordinates relative to its client-area.
/// </summary>
///
/// <param name="hWnd">
/// A handle to the target window.
/// </param>
///
/// <param name="button">
/// The mouse button message to send.
/// </param>
///
/// <param name="pt">
/// The coordinates within the client area of the window,
/// where to send the mouse button message.
/// </param>
///
/// <param name="async">
/// If <see langword="True"/>, calls PostMessage function rather than SendMessage.
/// </param>
///
/// <returns>
/// <see langword="True"/> if the function succeeds; otherwise, <see langword="False"/>.
/// </returns>
private static bool SendMouseButtonMessage_Internal(IntPtr hWnd, MouseButtonMessage button, Point pt, bool async) {
if (pt == null) {
pt = Point.Empty;
}
Delegate msgFunc = async ?
(Delegate)(new Func
<IntPtr, WindowMessages, IntPtr, IntPtr,
bool>(NativeMethods
.PostMessage)) : (Delegate)(new Func
<IntPtr, WindowMessages, IntPtr, IntPtr, IntPtr
>(NativeMethods
.SendMessage));
WindowMessages msg = (WindowMessages)button;
IntPtr lParam = UtilWin32.MakeLParam(pt.X, pt.Y);
IntPtr wParam = System.IntPtr.Zero;
switch (button) {
case MouseButtonMessage.LeftUp:
case MouseButtonMessage.RightUp:
case MouseButtonMessage.MiddleUp:
wParam = IntPtr.Zero;
break;
case MouseButtonMessage.LeftDown:
wParam
= new IntPtr
((int)WParams
.MK_LButton); break;
case MouseButtonMessage.RightDown:
wParam
= new IntPtr
((int)WParams
.MK_RButton); break;
case MouseButtonMessage.MiddleDown:
wParam
= new IntPtr
((int)WParams
.MK_MButton); break;
case MouseButtonMessage.WheelDown:
msg = WindowMessages.WM_MouseWheel;
wParam = UtilWin32.MakeLParam(0, -120);
break;
case MouseButtonMessage.WheelUp:
msg = WindowMessages.WM_MouseWheel;
wParam = UtilWin32.MakeLParam(0, 120);
break;
case MouseButtonMessage.WheelLeft:
msg = WindowMessages.WM_MouseHWheel;
wParam = UtilWin32.MakeLParam(0, -120);
break;
case MouseButtonMessage.WheelRight:
msg = WindowMessages.WM_MouseHWheel;
wParam = UtilWin32.MakeLParam(0, 120);
break;
default:
throw new InvalidEnumArgumentException
(nameof
(button
),
(int)button,
typeof(MouseButtonMessage
)); }
// Ensures the target window has focus.
NativeMethods.SetFocus(hWnd);
object success = msgFunc.DynamicInvoke(hWnd, msg, wParam, lParam);
return (async ? (bool)success : (IntPtr)success == IntPtr.Zero);
}
/// <summary>
/// *** FOR INTERNAL USE ONLY ***
/// <para></para>
/// Sends a mouse button click to the specified window,
/// on the specified coordinates relative to its client-area.
/// </summary>
///
/// <param name="hWnd">
/// A handle to the target window.
/// </param>
///
/// <param name="button">
/// The mouse button click to send.
/// </param>
///
/// <param name="pt">
/// The coordinates within the client area of the window,
/// where to send the mouse button click.
/// </param>
///
/// <param name="async">
/// If <see langword="True"/>, calls PostMessage function rather than SendMessage.
/// </param>
///
/// <returns>
/// <see langword="True"/> if the function succeeds; otherwise, <see langword="False"/>.
/// </returns>
private static bool SendMouseButtonClick_Internal(IntPtr hWnd, MouseButtonClick button, Point pt, bool async) {
MouseButtonMessage msgButtonDown;
MouseButtonMessage msgButtonUp;
switch (button) {
case MouseButtonClick.LeftButton:
msgButtonDown = MouseButtonMessage.LeftDown;
msgButtonUp = MouseButtonMessage.LeftUp;
break;
case MouseButtonClick.RightButton:
msgButtonDown = MouseButtonMessage.RightDown;
msgButtonUp = MouseButtonMessage.RightUp;
break;
case MouseButtonClick.MiddleButton:
msgButtonDown = MouseButtonMessage.MiddleDown;
msgButtonUp = MouseButtonMessage.MiddleUp;
break;
default:
throw new InvalidEnumArgumentException
(nameof
(button
),
(int)button,
typeof(MouseButtonClick
)); }
// Milliseconds to wait between sequentially sendig WM_#BUTTON_DOWN + WM_#BUTTON_UP message combination.
const int msgIntervalMs = 100;
bool successButtonDown = UtilMouse.SendMouseButtonMessage_Internal(hWnd, msgButtonDown, pt, async);
Thread.Sleep(msgIntervalMs);
bool successButtonUp = UtilMouse.SendMouseButtonMessage_Internal(hWnd, msgButtonUp, pt, async);
return successButtonDown & successButtonUp;
}
}
}
namespace Win32 {
/// <summary>
/// Platform Invocation (P/Invoke) methods, access unmanaged code.
/// </summary>
[SuppressUnmanagedCodeSecurity]
internal sealed class NativeMethods {
/// <summary>
/// Prevents a default instance of the <see cref="NativeMethods"/> class from being created.
/// </summary>
[DebuggerNonUserCode]
private NativeMethods() { }
#region User32.dll
/// <summary>
/// Sets the keyboard focus to the specified window.
/// <para></para>
/// The window must be attached to the calling thread's message queue.
/// </summary>
///
/// <param name="hWnd">
/// A handle to the window that will receive the keyboard input.
/// <para></para>
/// If this parameter is <see cref="IntPtr.Zero"/>, keystrokes are ignored.
/// </param>
///
/// <returns>
/// If the function succeeds, the return value is the handle to the window that previously had the keyboard focus.
/// <para></para>
/// If the <paramref name="hWnd"/> parameter is invalid or
/// the window is not attached to the calling thread's message queue,
/// the return value is <see cref="IntPtr.Zero"/>.
/// <para></para>
/// To get extended error information, call <see cref="Marshal.GetLastWin32Error"/>.
/// </returns>
[DllImport("User32.dll", SetLastError = true)]
internal static extern IntPtr SetFocus([In, Optional] IntPtr hWnd);
/// <summary>
/// Sends the specified message to a window or windows.
/// <para></para>
/// The SendMessage function calls the window procedure for the specified window
/// and does not return until the window procedure has processed the message.
/// </summary>
///
/// <param name="hWnd">
/// A handle to the window whose window procedure will receive the message.
/// </param>
///
/// <param name="msg">
/// The message to be sent.
/// </param>
///
/// <param name="wParam">
/// Additional message-specific information.
/// </param>
///
/// <param name="lParam">
/// Additional message-specific information.
/// </param>
///
/// <returns>
/// The return value specifies the result of the message processing; it depends on the message sent.
/// </returns>
[DllImport("User32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.SysInt)]
internal static extern IntPtr SendMessage([In, MarshalAs(UnmanagedType.SysInt)] IntPtr hWnd,
[In, MarshalAs(UnmanagedType.I4)] WindowMessages msg,
[In, MarshalAs(UnmanagedType.SysInt)] IntPtr wParam,
[In, MarshalAs(UnmanagedType.SysInt)] IntPtr lParam);
/// <summary>
/// Places (posts) a message in the message queue associated with the thread that created
/// the specified window and returns without waiting for the thread to process the message.
/// </summary>
///
/// <param name="hWnd">
/// Handle to the window whose window procedure will receive the message.
/// <para></para>
/// If this parameter is WindowMessages.HWND_Broadcast,
/// the message is sent to all top-level windows in the system,
/// including disabled or invisible unowned windows, overlapped windows, and pop-up windows;
/// but the message is not sent to child windows.
/// </param>
///
/// <param name="msg">
/// The message to be sent.
/// </param>
///
/// <param name="wparam">
/// Additional message-specific information.
/// </param>
///
/// <param name="lparam">
/// Additional message-specific information.
/// </param>
///
/// <returns>
/// If the function succeeds, the return value is <see langword="true"/>.
/// <para></para>
/// If the function fails, the return value is <see langword="false"/>.
/// <para></para>
/// To get extended error information, call <see cref="Marshal.GetLastWin32Error"/>.
/// </returns>
[DllImport("User32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool PostMessage([In, Optional, MarshalAs(UnmanagedType.SysInt)] IntPtr hWnd,
[In, MarshalAs(UnmanagedType.I4)] WindowMessages msg,
[In, MarshalAs(UnmanagedType.SysInt)] IntPtr wparam,
[In, MarshalAs(UnmanagedType.SysInt)] IntPtr lparam);
#endregion
}
/// <summary>
/// Provides Windows API related utilites.
/// </summary>
internal sealed class UtilWin32 {
/// <summary>
/// Prevents a default instance of the <see cref="UtilWin32"/> class from being created.
/// </summary>
[DebuggerNonUserCode]
private UtilWin32() { }
/// <summary>
/// Creates a LONG Param (LParam) value for a window message, from two <see cref="Integer"/> values.
/// <para></para>
/// You must call this method if you need to use negative values.
/// <para></para>
/// <seealso cref="Message.LParam"/>
/// </summary>
///
/// <remarks>
/// <see href="https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-makelparam"/>
/// </remarks>
///
/// <example> This is a code example.
/// <code language="VB.NET">
/// Dim lParam as IntPtr = MakeLParam(Integer.MaxValue, Integer.MaxValue)
/// </code>
/// </example>
///
/// <param name="lo">
/// The low-order <see cref="Integer"/> value.
/// </param>
///
/// <param name="hi">
/// The high-order <see cref="Integer"/> value.
/// </param>
///
/// <returns>
/// The resulting LParam value.
/// </returns>
[DebuggerStepThrough]
internal static IntPtr MakeLParam(int lo, int hi) {
byte[] loBytes = { BitConverter.GetBytes(lo)[0], BitConverter.GetBytes(lo)[1] };
byte[] hiBytes = { BitConverter.GetBytes(hi)[0], BitConverter.GetBytes(hi)[1] };
byte[] combined = loBytes.Concat(hiBytes).ToArray();
return new IntPtr
(BitConverter
.ToInt32(combined,
0)); }
/// <summary>
/// Creates a LONG Param (LParam) value for a window message, from two <see cref="UShort"/> values.
/// <para></para>
/// <seealso cref="Message.LParam"/>
/// </summary>
///
/// <remarks>
/// <see href="https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-makelparam"/>
/// </remarks>
///
/// <example> This is a code example.
/// <code language="VB.NET">
/// Dim lParam as IntPtr = MakeLParam(UShort.MaxValue, UShort.MaxValue)
/// </code>
/// </example>
///
/// <param name="loWord">
/// The low-order WORD value.
/// </param>
///
/// <param name="hiWord">
/// The high-order WORD value.
/// </param>
///
/// <returns>
/// The resulting LParam value.
/// </returns>
[DebuggerStepThrough]
internal static IntPtr MakeLParam(ushort loWord, ushort hiWord) {
return new IntPtr
(UtilWin32
.MakeDWord(loWord, hiWord
)); }
/// <summary>
/// Creates a Double WORD (DWORD, 32-Bit Unsigned Integer) value from a LOWORD and a HIWORD value.
/// </summary>
///
/// <example> This is a code example.
/// <code language="VB.NET">
/// Dim value as UInteger = MakeDWord(UShort.MaxValue, UShort.MaxValue)
/// </code>
/// </example>
///
/// <param name="loWord">
/// The low-order WORD.
/// </param>
///
/// <param name="hiWord">
/// The high-order WORD.
/// </param>
///
/// <returns>
/// The resulting DWORD value.
/// </returns>
[DebuggerStepThrough]
internal static uint MakeDWord(ushort loWord, ushort hiWord) {
byte[] loBytes = BitConverter.GetBytes(loWord);
byte[] hiBytes = BitConverter.GetBytes(hiWord);
byte[] combined = loBytes.Concat(hiBytes).ToArray();
return BitConverter.ToUInt32(combined, 0);
}
}
}
namespace Win32.Enums {
/// <summary>
/// Defines additional message-specific information for the WORD Param (WParam) value of window messages.
/// </summary>
///
/// <remarks>
/// <see href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms644927%28v=vs.85%29.aspx#system_defined"/>
/// </remarks>
internal enum WParams : int {
/// <summary>
/// A null WParam.
/// </summary>
Null = 0x0,
/// <summary>
/// The left mouse button is down.
/// <para></para>
/// wParam to use with <see cref="WindowMessages.WM_LButtonDown"/> message.
/// </summary>
MK_LButton = 0x1,
/// <summary>
/// The middle mouse button is down.
/// <para></para>
/// wParam to use with <see cref="WindowMessages.WM_LButtonDown"/> and <see cref="WindowMessages.WM_LButtonUp"/> messages.
/// </summary>
MK_MButton = 0x10,
/// <summary>
/// The right mouse button is down.
/// <para></para>
/// wParam to use with <see cref="WindowMessages.WM_LButtonDown"/> and <see cref="WindowMessages.WM_LButtonUp"/> messages.
/// </summary>
MK_RButton = 0x2
}
/// <summary>
/// Defines window messages.
/// <para></para>
/// The system sends or posts a system-defined message when it communicates with an application.
/// <para></para>
/// It uses these messages to control the operations of applications and to provide input and other information for applications to process.
/// <para></para>
/// An application can also send or post system-defined messages.
/// <para></para>
/// Applications generally use these messages to control the operation of control windows created by using preregistered window classes.
/// </summary>
///
/// <remarks>
/// <see href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms644927%28v=vs.85%29.aspx"/>
/// </remarks>
internal enum WindowMessages : int {
/// <summary>
/// The message is posted when the user presses the left mouse button while the cursor is in the
/// client area of a window.
/// <para></para>
/// If the mouse is not captured the message is posted to the window beneath the cursor.
/// <para></para>
/// Otherwise the message is posted to the window that has captured the mouse.
/// </summary>
WM_LButtonDown = 0x201,
/// <summary>
/// The message is posted when the user releases the left mouse button while the cursor is in the
/// client area of a window.
/// <para></para>
/// If the mouse is not captured the message is posted to the window beneath the cursor.
/// <para></para>
/// Otherwise the message is posted to the window that has captured the mouse.
/// </summary>
WM_LButtonUp = 0x202,
/// <summary>
/// The message is posted when the user presses the middle mouse button while the cursor is in the
/// client area of a window.
/// <para></para>
/// If the mouse is not captured the message is posted to the window beneath the cursor.
/// <para></para>
/// Otherwise the message is posted to the window that has captured the mouse.
/// </summary>
WM_MButtonDown = 0x207,
/// <summary>
/// The message is posted when the user releases the middle mouse button while the cursor is in the
/// client area of a window.
/// <para></para>
/// If the mouse is not captured the message is posted to the window beneath the cursor.
/// <para></para>
/// Otherwise the message is posted to the window that has captured the mouse.
/// </summary>
WM_MButtonUp = 0x208,
/// <summary>
/// The message is posted when the user presses the right mouse button while the cursor is in the
/// client area of a window.
/// <para></para>
/// If the mouse is not captured the message is posted to the window beneath the cursor.
/// <para></para>
/// Otherwise the message is posted to the window that has captured the mouse.
/// </summary>
WM_RButtonDown = 0x204,
/// <summary>
/// The message is posted when the user releases the right mouse button while the cursor is in the
/// client area of a window.
/// <para></para>
/// If the mouse is not captured the message is posted to the window beneath the cursor.
/// <para></para>
/// Otherwise the message is posted to the window that has captured the mouse.
/// </summary>
WM_RButtonUp = 0x205,
/// <summary>
/// Posted when the user presses the first or second X button while the cursor is in the client area of a window.
/// <para></para>
/// If the mouse is not captured, the message is posted to the window beneath the cursor.
/// <para></para>
/// Otherwise, the message is posted to the window that has captured the mouse.
/// </summary>
WM_XButtonDown = 0x20B,
/// <summary>
/// Posted when the user releases the first or second X button while the cursor is in the client area of a window.
/// <para></para>
/// If the mouse is not captured, the message is posted to the window beneath the cursor.
/// <para></para>
/// Otherwise, the message is posted to the window that has captured the mouse.
/// </summary>
WM_XButtonUp = 0x20C,
/// <summary>
/// The message is sent to the focus window when the mouse wheel is rotated.
/// <para></para>
/// The <c>DefWindowProc</c> function propagates the message to the window's parent.
/// <para></para>
/// There should be no internal forwarding of the message since <c>DefWindowProc</c> propagates it up the
/// parent chain until it finds a window that processes it.
/// </summary>
WM_MouseWheel = 0x20A,
/// <summary>
/// The message is sent to the focus window when the mouse's horizontal scroll wheel is tilted or rotated.
/// <para></para>
/// The <c>DefWindowProc</c> function propagates the message to the window's parent.
/// <para></para>
/// There should be no internal forwarding of the message since <c>DefWindowProc</c> propagates it up
/// the parent chain until it finds a window that processes it.
/// </summary>
WM_MouseHWheel = 0x20E
}
}
}