c# - 如何在 Unity 中实现和使用低级键盘 Hook 来禁用 Windows 快捷方式?

标签 c# windows unity3d

我的问题

如何在 Unity 中实现和使用低级键盘 Hook 来禁用 Windows 快捷方式?

我想防止用户因意外使用 Windows 键而失去对我的游戏的关注。这是因为我的应用程序是为可以随意按下键盘的幼儿设计的。

来自 searching stack overflow看来我需要实现一个低级键盘 Hook 。

我尝试过的

以下已在 Unity 中实现。当按下打印屏幕按钮时,它应该将我的应用程序的背景颜色变成黑色,证明我已经正确实现了它。但是,在测试我是否可以使用它捕获键盘输入时,我发现这段代码

        Debug.Log("Print Screen");
        Camera cam = FindObjectOfType<Camera>();
        cam.backgroundColor = Color.black; 

没有被调用,背景颜色也没有变成黑色。

代码

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace SnagFree.TrayApp.Core
{
    class GlobalKeyboardHookEventArgs : HandledEventArgs
    {
        public GlobalKeyboardHook.KeyboardState KeyboardState { get; private set; }
        public GlobalKeyboardHook.LowLevelKeyboardInputEvent KeyboardData { get; private set; }

        public GlobalKeyboardHookEventArgs(
            GlobalKeyboardHook.LowLevelKeyboardInputEvent keyboardData,
            GlobalKeyboardHook.KeyboardState keyboardState)
        {
            KeyboardData = keyboardData;
            KeyboardState = keyboardState;
        }
    }

    //Based on https://gist.github.com/Stasonix
    class GlobalKeyboardHook : IDisposable
    {
        public event EventHandler<GlobalKeyboardHookEventArgs> KeyboardPressed;

        public GlobalKeyboardHook()
        {
            _windowsHookHandle = IntPtr.Zero;
            _user32LibraryHandle = IntPtr.Zero;
            _hookProc = LowLevelKeyboardProc; // we must keep alive _hookProc, because GC is not aware about SetWindowsHookEx behaviour.

            _user32LibraryHandle = LoadLibrary("User32");
            if (_user32LibraryHandle == IntPtr.Zero)
            {
                int errorCode = Marshal.GetLastWin32Error();
                throw new Win32Exception(errorCode, $"Failed to load library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
            }



            _windowsHookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, _hookProc, _user32LibraryHandle, 0);
            if (_windowsHookHandle == IntPtr.Zero)
            {
                int errorCode = Marshal.GetLastWin32Error();
                throw new Win32Exception(errorCode, $"Failed to adjust keyboard hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
            }
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // because we can unhook only in the same thread, not in garbage collector thread
                if (_windowsHookHandle != IntPtr.Zero)
                {
                    if (!UnhookWindowsHookEx(_windowsHookHandle))
                    {
                        int errorCode = Marshal.GetLastWin32Error();
                        throw new Win32Exception(errorCode, $"Failed to remove keyboard hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
                    }
                    _windowsHookHandle = IntPtr.Zero;

                    // ReSharper disable once DelegateSubtraction
                    _hookProc -= LowLevelKeyboardProc;
                }
            }

            if (_user32LibraryHandle != IntPtr.Zero)
            {
                if (!FreeLibrary(_user32LibraryHandle)) // reduces reference to library by 1.
                {
                    int errorCode = Marshal.GetLastWin32Error();
                    throw new Win32Exception(errorCode, $"Failed to unload library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
                }
                _user32LibraryHandle = IntPtr.Zero;
            }
        }

        ~GlobalKeyboardHook()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private IntPtr _windowsHookHandle;
        private IntPtr _user32LibraryHandle;
        private HookProc _hookProc;

        delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("kernel32.dll")]
        private static extern IntPtr LoadLibrary(string lpFileName);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        private static extern bool FreeLibrary(IntPtr hModule);

        /// <summary>
        /// The SetWindowsHookEx function installs an application-defined hook procedure into a hook chain.
        /// You would install a hook procedure to monitor the system for certain types of events. These events are
        /// associated either with a specific thread or with all threads in the same desktop as the calling thread.
        /// </summary>
        /// <param name="idHook">hook type</param>
        /// <param name="lpfn">hook procedure</param>
        /// <param name="hMod">handle to application instance</param>
        /// <param name="dwThreadId">thread identifier</param>
        /// <returns>If the function succeeds, the return value is the handle to the hook procedure.</returns>
        [DllImport("USER32", SetLastError = true)]
        static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);

        /// <summary>
        /// The UnhookWindowsHookEx function removes a hook procedure installed in a hook chain by the SetWindowsHookEx function.
        /// </summary>
        /// <param name="hhk">handle to hook procedure</param>
        /// <returns>If the function succeeds, the return value is true.</returns>
        [DllImport("USER32", SetLastError = true)]
        public static extern bool UnhookWindowsHookEx(IntPtr hHook);

        /// <summary>
        /// The CallNextHookEx function passes the hook information to the next hook procedure in the current hook chain.
        /// A hook procedure can call this function either before or after processing the hook information.
        /// </summary>
        /// <param name="hHook">handle to current hook</param>
        /// <param name="code">hook code passed to hook procedure</param>
        /// <param name="wParam">value passed to hook procedure</param>
        /// <param name="lParam">value passed to hook procedure</param>
        /// <returns>If the function succeeds, the return value is true.</returns>
        [DllImport("USER32", SetLastError = true)]
        static extern IntPtr CallNextHookEx(IntPtr hHook, int code, IntPtr wParam, IntPtr lParam);

        [StructLayout(LayoutKind.Sequential)]
        public struct LowLevelKeyboardInputEvent
        {
            /// <summary>
            /// A virtual-key code. The code must be a value in the range 1 to 254.
            /// </summary>
            public int VirtualCode;

            /// <summary>
            /// A hardware scan code for the key. 
            /// </summary>
            public int HardwareScanCode;

            /// <summary>
            /// The extended-key flag, event-injected Flags, context code, and transition-state flag. This member is specified as follows. An application can use the following values to test the keystroke Flags. Testing LLKHF_INJECTED (bit 4) will tell you whether the event was injected. If it was, then testing LLKHF_LOWER_IL_INJECTED (bit 1) will tell you whether or not the event was injected from a process running at lower integrity level.
            /// </summary>
            public int Flags;

            /// <summary>
            /// The time stamp stamp for this message, equivalent to what GetMessageTime would return for this message.
            /// </summary>
            public int TimeStamp;

            /// <summary>
            /// Additional information associated with the message. 
            /// </summary>
            public IntPtr AdditionalInformation;
        }

        public const int WH_KEYBOARD_LL = 13;
        //const int HC_ACTION = 0;

        public enum KeyboardState
        {
            KeyDown = 0x0100,
            KeyUp = 0x0101,
            SysKeyDown = 0x0104,
            SysKeyUp = 0x0105
        }

        public const int VkSnapshot = 0x2c;
        //const int VkLwin = 0x5b;
        //const int VkRwin = 0x5c;
        //const int VkTab = 0x09;
        //const int VkEscape = 0x18;
        //const int VkControl = 0x11;
        const int KfAltdown = 0x2000;
        public const int LlkhfAltdown = (KfAltdown >> 8);

        public IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam)
        {
            bool fEatKeyStroke = false;

            var wparamTyped = wParam.ToInt32();
            if (Enum.IsDefined(typeof(KeyboardState), wparamTyped))
            {
                object o = Marshal.PtrToStructure(lParam, typeof(LowLevelKeyboardInputEvent));
                LowLevelKeyboardInputEvent p = (LowLevelKeyboardInputEvent)o;

                var eventArguments = new GlobalKeyboardHookEventArgs(p, (KeyboardState)wparamTyped);

                EventHandler<GlobalKeyboardHookEventArgs> handler = KeyboardPressed;
                handler?.Invoke(this, eventArguments);

                fEatKeyStroke = eventArguments.Handled;
            }

            return fEatKeyStroke ? (IntPtr)1 : CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
        }
    }
}

用法:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


namespace SnagFree.TrayApp.Core

{

internal class Controller : MonoBehaviour
{
    public bool printScreen = false;

    private GlobalKeyboardHook _globalKeyboardHook;

    public void SetupKeyboardHooks()
    {
        _globalKeyboardHook = new GlobalKeyboardHook();
        _globalKeyboardHook.KeyboardPressed += OnKeyPressed;
    }

    private void OnKeyPressed(object sender, GlobalKeyboardHookEventArgs e)
    {

        //Debug.WriteLine(e.KeyboardData.VirtualCode);

        if (e.KeyboardData.VirtualCode != GlobalKeyboardHook.VkSnapshot)
            return;

        // seems, not needed in the life.
        //if (e.KeyboardState == GlobalKeyboardHook.KeyboardState.SysKeyDown &&
        //    e.KeyboardData.Flags == GlobalKeyboardHook.LlkhfAltdown)
        //{
        //    MessageBox.Show("Alt + Print Screen");
        //    e.Handled = true;
        //}
        //else

        if (e.KeyboardState == GlobalKeyboardHook.KeyboardState.KeyDown)
        {

            e.Handled = true;
            Debug.Log("Print Screen");
            Camera cam = FindObjectOfType<Camera>();
            cam.backgroundColor = Color.black;
        }
    }

    public void Dispose()
    {
        _globalKeyboardHook?.Dispose();
    }
}
}

最佳答案

要禁用/丢弃或忽略低级输入,您必须使用原始输入

详情在这里:https://learn.microsoft.com/en-us/windows/desktop/inputdev/raw-input

但是在 Unity 中使用它不是一个好主意!

解释为什么这不是一个好主意(在评论中询问):

使用统一“板载”工具,您无法禁用/忽略 Windows 键或其他组合等关键事件,因为您的操作系统将首先处理这些事件,然后将这些事件传递给您的程序,例如 Unity/独立播放器。要在 Unity 中使用不安全的代码(导入 W32Libs),您必须注意自己在做什么。没有人能阻止您造成内存泄漏。此外,Unity 并非旨在采用这种方式,这可能会导致运行时(即使在编辑器中)出现不稳定且非常奇怪的行为

此外,当您从您的操作系统/环境中删除用户通常使用的工作行为时,您将开始烦扰您的用户,例如当“打开任务管理器”功能由于 key 被删除而无法访问时,当应用程序崩溃时您将如何关闭它?我怎样才能切换到我的桌面。

这就是为什么我写道“但在 Unity 中使用它不是一个好主意!”

你可以,但如果你必须寻求一种方法来做到这一点,你可能没有在不损害你的系统或用户的情况下使用它的经验:)

关于c# - 如何在 Unity 中实现和使用低级键盘 Hook 来禁用 Windows 快捷方式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54160953/

相关文章:

unity3d - 如何在 Unity 3d 的纹理上设置图像

algorithm - 如何在 Unity 中对齐 "tracks"或模块化对象?

c# - 我们如何使用 Selenium Webdriver C# 从 URL 获取特定值?

python - 在 Python 3 中将 ascii 字符输出到标准输出

c# - 作为 Windows 服务运行时的 PCSC.InvalidContextException

c# - 将窗口置于前台的问题

javascript - 如何在使用 Javascript/C# 在 CKEditor 中键入特定字符(如 Enter 键或 Esc 键)时显示警告消息?

c# - 自托管 wcf 添加服务引用

c# - ASP.NET Core 2 中多个相同类型实例的依赖注入(inject)

ios - 如何为 iOS 版 Unity3d 添加辅助功能