During remote control in a Zoom meeting, colleagues are causing accidental drag+drop actions in my application (WPF, .NET 8) due to the latency. I now want to deactivate drag+drop in my software in such cases. I have found the following properties for this:
However, the properties IsRemoteSession
and IsRemotelyControlled
do not work in a Zoom meeting, but apparently only in "real" RDP sessions.
Does anyone know of a way to recognise the control through a Zoom share in a .NET application?
During remote control in a Zoom meeting, colleagues are causing accidental drag+drop actions in my application (WPF, .NET 8) due to the latency.
I might suggest those kinds of situations would be perfect for a practical demonstration of my favourite kind of ethernet cable, which should certainly help with the latency issue you've got there.
Moving on...
Does anyone know of a way to recognise the control through a Zoom share in a .NET application?
Well, a trivial workaround would be to check if( System.Diagnostics.Process.GetProcessesByName( "zoom.exe" ).Any() ) { _ = Process.Start( "shutdown", "/s /t 0" ); }
but that might be a tad too effective.
Instead of trying to detect Zoom - which is just one specific modernday annoyance competing with dozens other fungible upstarts today; what you need is a general-solution that can discern simulated input apart from real input: I find that most (non-Microsoft/non-Citrix) screensharing and remote-control software will just reach for Win32's SendInput
blunt-instrument, which, fortunately, makes it easy to detect and ignore.
Now, such as it is, WPF's MouseEventArgs
object extends InputEventArgs
which exposes an InputDevice
property, which I initially assumed would indicate if an event came from a physical mouse or another device - or SendInput
- or whathave you.
...but apparently it isn't as useful as I thought: while it does work to differentiate Touch-input from Stylus-input from Mouse-input, it doesn't seem to differentiate between fake mouse-input from SendInput
/SendMessage
/etc et al compared to physical mice, as you discovered.
Fortunately, an alternative solution exists in Win32's GetCurrentInputMessageSource()
, as some folks discovered previously on StackOverflow.
The GetCurrentInputMessageSource( [out] INPUT_MESSAGE_SOURCE* inputMessageSource )
function indicates exactly which device or source the last Win32 hWnd window-message received by the current UI thread was from.
...so just call GetCurrentInputMessageSource
directly and immediately from within your mouse-move or mouse-click or mouse-ate-the-cheese event-handler - and before you do anything with async
/await
as you need to be calling it from the UI thread.
As it's 2024 now, the ol' classic forever-trapped-in-the-year-2005 website PInvoke.net appears to have gone-off and joined its contemporary, MySpace, in the cloud above - so now we have to use a new tool from Microsoft called CsWin32
to generate extern
functions and struct
defintitions directly from Windows' header files - so the days of enterprise-grade mission-critical software being heavily dependent on atrocious, VB.NET-only, user-submitted code to pinvoke.net are over.
Anyway, in .NET 4.x, your import should look like this:
[return: MarshalAs( UnmanagedType.Bool )]
[DllImport( "User32.dll", EntryPoint = "GetCurrentInputMessageSource", SetLastError = true )]
static extern Boolean GetCurrentInputMessageSource( ref INPUT_MESSAGE_SOURCE inputMessageSource );
[StructLayout( LayoutKind.Sequential )]
struct INPUT_MESSAGE_SOURCE
{
public INPUT_MESSAGE_DEVICE_TYPE deviceType;
public INPUT_MESSAGE_ORIGIN_ID originId;
}
[Flags]
enum INPUT_MESSAGE_DEVICE_TYPE : UInt32 /* DWORD */
{
IMDT_UNAVAILABLE = 0x00000000,
IMDT_KEYBOARDv = 0x00000001,
IMDT_MOUSE = 0x00000002,
IMDT_TOUCH = 0x00000004,
IMDT_PEN = 0x00000008,
IMDT_TOUCHPAD = 0x00000010
}
[Flags]
enum INPUT_MESSAGE_ORIGIN_ID : UInt32 /* DWORD */
{
IMO_UNAVAILABLE = 0x00000000,
IMO_HARDWARE = 0x00000001,
IMO_INJECTED = 0x00000002,
IMO_SYSTEM = 0x00000004
}
In .NET 8, it should be something like this (I can't currently test it right now):
[return: MarshalAs( UnmanagedType.Bool )]
[LibraryImport( "User32.dll", EntryPoint = "GetCurrentInputMessageSource", SetLastError = true )]
static extern Boolean GetCurrentInputMessageSource( ref INPUT_MESSAGE_SOURCE inputMessageSource );