NickAc
NickAc

Reputation: 25

Global low-level mouse hook doesn't get called when UWP desktop app is out of focus

I'm creating a desktop UWP app and I need to set a global low level mouse hook to detect and change the position of it when it is moved to certain locations of the screen.

It works fine while my app's window is in focus and the values get logged correctly to the output window(meaning that the hook is working correctly).

This UWP app isn't going to be on the store and will only be used on a Windows 10 desktop (1903+).

I've tried calling SetWindowsHookEx on a different thread (which didn't do anything). Tried also passing a thread ID when calling SetWindowsHookEx to no avail. Also tried using the following restricted capabilities to prevent the app to suspend when not on focus: extendedExecutionUnconstrained and extendedBackgroundTaskTime along with the PreventFromSuspending method shown here. Another thing I tried was to set uiAccess to true on the app manifest, which also didn't work.

The global hook is supposed to work even when the app isn't in the foreground, but instead it just works when it has the active window focus.

#region Structures

[StructLayout(LayoutKind.Sequential)]
/* MSLLHOOKSTRUCT */
public struct NativeMouseLowLevelHook
{
    public override string ToString()
    {
        return $"{nameof(Point)}: {Point}, {nameof(MouseData)}: {MouseData}, {nameof(Flags)}: {Flags}";
    }

    public NativePoint Point;
    public int MouseData;
    public int Flags;
    public int Time;
    public UIntPtr ExtraInfo;
}

[StructLayout(LayoutKind.Sequential)]
public class NativePoint
{
    public override string ToString()
    {
        return $"{nameof(X)}: {X}, {nameof(Y)}: {Y}";
    }

    public int X;
    public int Y;
}

#endregion
#region Hook

public class ManagedMouseHook : SafeHandleZeroOrMinusOneIsInvalid
{
    public static List<ManagedMouseHook> KnownHooks { get; set; } = new List<ManagedMouseHook>();

    public HookProc HookImpl { get; set; }

    public ManagedMouseHook() : base(true)
    {
        Hook();
    }

    private void Hook()
    {
        HookImpl = NativeHookCallback;
        KnownHooks.Add(this);
        using (var curProcess = Process.GetCurrentProcess())
        using (var curModule = curProcess.MainModule)
        {
            DoHook(curModule);
        }
    }

    private void DoHook(ProcessModule curModule)
    {
        SetHandle(SetWindowsHookEx(14 /*WH_MOUSE_LL*/, HookImpl, GetModuleHandle(curModule.ModuleName), 0));
    }

    private bool UnHook()
    {
        var result = UnhookWindowsHookEx(DangerousGetHandle());
        KnownHooks.Remove(this);
        HookImpl = null;
        return result;
    }

    /* LowLevelMouseProc */
    private IntPtr NativeHookCallback(int code, IntPtr wparam, IntPtr lparam)
    {
        if (code >= 0)
        {
            var info = (NativeMouseLowLevelHook) Marshal.PtrToStructure(lparam,
                typeof(NativeMouseLowLevelHook));

            Debug.WriteLine(info); //Output example: Point: X: 408, Y: 535, MouseData: 0, Flags: 0
            return new IntPtr(-1);
        }

        return CallNextHookEx(IntPtr.Zero, code, wparam, lparam);
    }

    protected override bool ReleaseHandle()
    {
        return UnHook();
    }
}

#endregion
#region Interop

public delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);

[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(int hookType, HookProc lpfn, IntPtr hMod, ulong dwThreadId);

[DllImport("user32.dll", SetLastError = true)]
public static extern bool UnhookWindowsHookEx(IntPtr hhk);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);

[DllImport("user32.dll")]
public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam,
    IntPtr lParam);

#endregion

Then, to start using it:

public ManagedMouseHook MouseHook { get; set; }
MouseHook/*property*/ = new ManagedMouseHook();

And to unhook:

MouseHook/*property*/.Close();

Upvotes: 2

Views: 802

Answers (2)

NickAc
NickAc

Reputation: 25

I've decided with the help of the comments that the best way to do this is to use my UWP app as a front-end and using a win32 app for the hooking functionality itself.

Upvotes: 0

Ivan I
Ivan I

Reputation: 9990

UWP apps are running in the sandbox, so that doesn't sound strange. If you think a bit it is a security problem if the app can receive such an input and as 'Security' is listed as No.1 characteristic of the UWP this behavior is expected.

Upvotes: 1

Related Questions