简体   繁体   English

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

[英]How do I implement and use a low-level keyboard hook in Unity to disable windows shortcuts?

My Question 我的问题

How do I implement and use a low-level keyboard hook in Unity to disable windows shortcuts? 如何在Unity中实现和使用低级键盘钩子来禁用Windows快捷方式?

I would like to prevent the user being able to lose focus of my game through accidental use of the windows key. 我想通过意外使用Windows密钥来防止用户失去我的游戏焦点。 This is because my application is designed for toddlers who can press the keyboard quite randomly. 这是因为我的应用程序是专为可以随机按键盘的幼儿设计的。

From searching stack overflow it seems that I need to implement aa low-level keyboard hook. 搜索堆栈溢出看来,我需要实现一个低级键盘钩子。

What I have tried 我试过了什么

The below has been implemented in Unity. 以下内容已在Unity中实施。 When pressing the print screen button it should turn the background color of my application black proving that I have implemented it correctly. 当按下打印屏幕按钮时,它应该将我的应用程序的背景颜色变为黑色,证明我已正确实现它。 However, when testing to see if I can capture keyboard inputs using this I have found that this block of code 但是,在测试时,看看我是否可以使用此捕获键盘输入,我发现了这段代码

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

does not get called and the background colour does not change to black. 没有被调用,背景颜色不会变为黑色。

The code 代码

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

Usage: 用法:

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

to disable/discard or ignore low level Input you have to use Raw Input 要禁用/放弃或忽略低级输入,您必须使用原始输入

Details here: https://docs.microsoft.com/en-us/windows/desktop/inputdev/raw-input 详情请访问: https//docs.microsoft.com/en-us/windows/desktop/inputdev/raw-input

but that's not a good idea to use this in Unity! 但是在Unity中使用它并不是一个好主意!

Explanation why it is not a good Idea (Asked in comment): 解释为什么它不是一个好主意(在评论中提问):

With unity "onboard" tools you cannot disable/ignore key events like Windows key or other combinations because your OS will first process these events and AFTER this, it will pass those to your program like Unity / stand-alone player. 使用统一的“板载”工具,您无法禁用/忽略Windows键或其他组合等关键事件,因为您的操作系统将首先处理这些事件,在此之后,它会将这些事件传递给您的程序,如Unity /独立播放器。 To work with unsafe code (import W32Libs) in Unity you have to take care of what you are doing. 要在Unity中使用不安全的代码(导入W32Libs),您必须处理您正在做的事情。 No one prevents you from creating memory leaks. 没有人阻止你创建内存泄漏。 Also Unity is not designed to go this way, this can lead to unstable and very weird behavior on runtime (even in the Editor) Unity也不是这样设计的,这可能导致运行时出现不稳定和非常奇怪的行为(即使在编辑器中)

Also you will start annoying your users when you remove working behaviors from your OS/Environment that they use normally eg when the functionality "Open Task Manager" is inaccessible due to the keys were removed how would you close the App when it crashed? 当你从他们正常使用的操作系统/环境中删除工作行为时,你会开始讨厌你的用户,例如当由于键被删除而无法访问“打开任务管理器”功能时如何在崩溃时关闭应用程序? how can I switch to my Desktop. 如何切换到我的桌面。

This is why I wrote "but that's not a good idea to use this in Unity!" 这就是我写“但在Unity中使用它不是一个好主意”的原因。

You can but if you have to ask for a way to do that, you may not have the experience to use it without harm to your system or your users :) 你可以,但如果你不得不要求一种方法,你可能没有经验使用它而不会损害你的系统或用户:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何使用低级8位标志作为条件? - How do I use low-level 8 bit flags as conditionals? Windows 8中的某些窗口使我的低级键盘挂钩无法正常工作 - Some windows in Windows 8 prevent my low-level keyboard hook from working 是否可以检测到 Windows 何时自动断开低级键盘挂钩? - Is it possible to detect when a low-level keyboard hook has been automatically disconnected by Windows? C#低级键盘挂钩不工作 - C# Low-Level Keyboard Hook Not Working 如何使用C#中的低级键盘挂钩抑制任务切换键(winkey,alt-tab,alt-esc,ctrl-esc) - How to Suppress task switch keys (winkey, alt-tab, alt-esc, ctrl-esc) using low-level keyboard hook in c# 什么可以导致Windows取消挂钩低级别(全局)键盘钩子? - What can cause Windows to unhook a low level (global) keyboard hook? 计时器上的低级键盘挂钩 - Low level keyboard hook on a timer 使用低级键盘挂钩抑制任务切换键(winkey,alt-tab,alt-esc,ctrl-esc) - Suppress task switch keys (winkey, alt-tab, alt-esc, ctrl-esc) using low-level keyboard hook 函数 UnhookWindowsHookEx,如果在低级钩子应用程序中被遗漏了怎么办? - Function UnhookWindowsHookEx, what if it is left out in a low-level hook application? 如何在Silverlight上实现底层绑定? - How realized binding in silverlight on low-level?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM