.netwpfremote-access.net-8.0

Detect Zoom-Control in .NET 8 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 now want to deactivate drag+drop in my software in such cases. I have found the following properties for this:

IsRemoteSession

IsRemotelyControlled

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?


Solution

  • 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.

    Get it from NuGet.

    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 );