Регистрация | Вход 
Просмотр статьи
Глобальный хук на клавиатуру

Установка хуков и C#

Все началось с того, что, печатая код в полноэкранном режиме, меня доставало переключение языков. У меня 3 раскладки, в коде нужно часто вставлять комментарии и другое. Много времени теряю на это, потому решил совместить следующую статью о хуках с этой проблемой.

Идея предельно проста, наша программа «сидит» в трее, при изменении раскладки клавиатуры мы показываем нужный флаг в определенной части экрана, с определенным размером.

Хуки? А что это?

Для начала, разберемся с хуками.

Hook – точка в механизме обработки сообщений, где мы можем следить за сообщениями какого-то потока, или всех существующих.

Существует много различных типов точек обработки сообщений в ОС Windows. Каждая из них существует для определенных сообщений. Например, мы можем следить только за сообщениями от клавиатуры (WH_KEYBOARD), или же за сообщениями от мыши (WH_MOUSE).

Чтобы создать хук, вам нужно сделать:

  • Создать хук, установить его в цепочку обработки сообщений.
  • Создать специальную функцию повторного вызова (callback-функция), которая также называется подключаемой процедурой.
  • Все подключаемые процедуры имеют такой вид:
LRESULT CALLBACK HookProc
(
int nCode,
WPARAM wParam,
LPARAM lParam
)
  • nCode – код hook-точки, который подключаемая процедура использует для идентификации действия.
  • wParam – зависит от hook-точки.
  • lParam – зависит от hook-точки.
  • Для установки подключаемой процедуры в начало цепочки hook-точек, нам нужно вызвать WinApi-функцию SetWindowsHookEx.
HHOOK SetWindowsHookEx(      
    int idHook,
    HOOKPROC lpfn,
    HINSTANCE hMod,
    DWORD dwThreadId
);
 
  • idHook – тип hook-процедуры (WH_MOUSE, WH_KEYBOARD…)
  • lpfn – Подключаемая процедура, callback-функция.
  • hMod – handle для DLL, в которой будем ставить хук и обрабатывать его. Если нужно сделать локальный хук, этот параметр нужно поставить в NULL
  • dwThreadId – идентификатор потока, с которым ассоциировать подключаемую процедуру. Если нужно контролировать все поток, этот параметр должен быть 0.

И еще одна важная часть, в hook procedure нам нужно вызывать ф-цию CallNextHookEx, она передает управление другой процедуре (если это нужно).

Думаю, теперь у вас хватит знаний, чтобы разобраться с кодом.

Пишем основную часть приложения

Давайте продумаем принцип работы программы: у нас, в папке с программой, лежат рисунки флагов. При получении нажатия на клавиатуре, мы сверяем, сменился ли язык ввода. Если изменения есть: мы создаем новое окно, передаем ему адрес на нужный рисунок, и, учитывая все настройки(прозрачность флага, время показа, размер, положение) показываем форму с флагом на фоне.

Для начала, нужно создать класс, в котором будут все настройки программы. Создайте новый класс Settings.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace OckShowKeyboardState
{
    class Settings
    {
        // Последний используемый язык
        public static string LastInputLanguage = "USA";
        // Путь к папке с языками
        public static string FlagsPath = "flags/";
        // Время, сколько показывать окно с флагом 
        public static int ShowMiliseconds = 600;
        // Прозрачность окна от 0 до 1 (полная непрозрачность)
        public static double OpacityFlag = 0.8;

        // Позиция вспылвающего окна
        public static int XShowWindowPos = 0;
        public static int YShowWindowPos = 0;

        // Размеры изображения флага
        public static int WFlagSize = 100;
        public static int HFlagSize = 65;

        public static bool NowShowFlag = false;
    }
}

Теперь создайте Windows-Form, имя: ShowFlag

Укажите его параметры:

  • BackgroundImageLayout – Stretch
  • FormBorderStyle – None
  • ShowInTaskbar – None
  • StartPosition – Manual
  • TopMost - True

Также поместите на форму таймер (UpdateForm), по которому эта же форма будет закрываться. Вот код формы:

namespace OckShowKeyboardState
{
    public partial class ShowFlag : Form
    {
        public ShowFlag(string BgImagePath, IntPtr hWnd)
        {
            InitializeComponent();

            Bitmap FlagImage = new Bitmap(BgImagePath);
            this.BackgroundImage = FlagImage;

            // Далее здесь делаем прозрачность и настраиваем размеры флага!!!
            this.Width = Settings.WFlagSize;
            this.Height = Settings.HFlagSize;
 
            this.Location = new Point(Settings.XShowWindowPos; Settings.YShowWindowPos);
            this.Opacity = Settings.OpacityFlag;
            UpdateForm.Interval = Settings.ShowMiliseconds;
            UpdateForm.Enabled = true;
		  // Делаем окно активным, чтобы управление вернулось к нему
            Hooks.SetForegroundWindow(hWnd);
        }
        private void UpdateForm_Tick(object sender, EventArgs e)
        {
            Settings.NowShowFlag = false;
            this.Close();
        }
    }
}

Как всегда, пишем «ядро» программы. Здесь мы будем ставить ставить, обрабатывать hook-процедуру.

Создайте новый проект. Добавьте новый класс Hooks.cs Все комментарии и объяснения в коде:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Threading;

namespace OckShowKeyboardState
{
    class Hooks
    {
        // -----------------------------------------------------------------------------------
	    // Этот делегает и есть подключаемая процедура.
        private delegate IntPtr keyboardHookProc(int code, IntPtr wParam, IntPtr lParam);
        // -----------------------------------------------------------------------------------
        const int WH_KEYBOARD_LL = 13;	// Номер глобального LowLevel-хука на клавиатуру
        const int WM_KEYDOWN = 0x100;	// Сообщения нажатия клавиши
        // -----------------------------------------------------------------------------------
	    // Создаем один экземпляр процедуры, назначаем ф-цию для делегата
        private static keyboardHookProc _proc = hookProc;

        // Объект хука
        private static IntPtr hhook = IntPtr.Zero;

        // Это окно, через которое будем показывать флаги
        private static ShowFlag WindowShowFlag;

	    // Идентификатор активного потока
        private static int _ProcessId;

        // Получаем все установленные в систему языки
        private static InputLanguageCollection _InstalledInputLanguages;

	    // Текущий язык ввода
        private static string _CurrentInputLanguage;
        // -----------------------------------------------------------------------------------
        public static void SetHook()
        {
            // Получаем handle нужной библиотеки
            IntPtr hInstance = LoadLibrary("User32");
            // Ставим LowLevel-hook, обработки клавиатуры, на все потоки
            hhook = SetWindowsHookEx(WH_KEYBOARD_LL, _proc, hInstance, 0);
        }
        // -----------------------------------------------------------------------------------
        public static void UnHook()
        {
		 // Снятие хука
            UnhookWindowsHookEx(hhook);
        }
        // -----------------------------------------------------------------------------------
        // Получение раскладки клавиатуры активного окна
        private static string GetKeyboardLayoutId()
        {
            _InstalledInputLanguages = InputLanguage.InstalledInputLanguages;

            // Получаем хендл активного окна
            IntPtr hWnd = GetForegroundWindow();
            // Получаем номер потока активного окна
            int WinThreadProcId = GetWindowThreadProcessId(hWnd, out _ProcessId);
            // Получаем раскладку
            IntPtr KeybLayout = GetKeyboardLayout(WinThreadProcId);
		 // Циклом перебираем все установленные языки для проверки идентификатора
            for (int i = 0; i < _InstalledInputLanguages.Count; i++)
            {
                if (KeybLayout == _InstalledInputLanguages[i].Handle)
                {
                    _CurrentInputLanguage =    _InstalledInputLanguages[i].Culture.ThreeLetterWindowsLanguageName.ToString();
                }
            }
            return _CurrentInputLanguage;
        }
        // -----------------------------------------------------------------------------------
	    // Вот и наша подключаемая процедура. 
// Здесь мы будем обрабатывать сообщения клавиатуры
public static IntPtr hookProc(int code, IntPtr wParam, IntPtr lParam) { if (code >= 0 && wParam == (IntPtr)WM_KEYUP) { // Получаем handle активного окна IntPtr hWnd = GetForegroundWindow(); // Получаем раскладку активного окна string NowLanguage = GetKeyboardLayoutId(); // Совмещаем путь, для получения изображения флага string Path = Settings.FlagsPath + GetKeyboardLayoutId() + ".GIF"; // Если уже идет показ флага, просто обновляем все значения if (Settings.NowShowFlag) { // Если сейчас показывается флаг, просто обновляем значение Settings.LastInputLanguage = NowLanguage; } if (Settings.LastInputLanguage != NowLanguage) { Settings.NowShowFlag = true; // Создаем экземпляр нового окна, в котором будем показывать флаг WindowShowFlag = new ShowFlag(Path, hWnd); // Изменяем последний полученный язык Settings.LastInputLanguage = NowLanguage; // Показ флага WindowShowFlag.Show(); } } return CallNextHookEx(hhook, code, (int)wParam, lParam); } // -------------------------------------------------------------------------------------- // Импорт нужных WinApi-функций [DllImport("user32.dll")] static extern IntPtr SetWindowsHookEx(int idHook, keyboardHookProc callback, IntPtr hInstance, uint threadId); [DllImport("user32.dll")] static extern bool UnhookWindowsHookEx(IntPtr hInstance); [DllImport("user32.dll")] static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, int wParam, IntPtr lParam); [DllImport("kernel32.dll")] static extern IntPtr LoadLibrary(string lpFileName); [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern int GetWindowThreadProcessId(IntPtr handleWindow, out int lpdwProcessID); [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern IntPtr GetKeyboardLayout(int WindowsThreadProcessID); [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern IntPtr SetForegroundWindow(IntPtr hWnd); } }

Как видите, здесь я использую импортирование WinApi функций. Делается это просто: посмотрите в MSDN имя длл, в которой расположена нужная функция, потом в проект нужно подключить

using System.Runtime.InteropServices;

чтобы использовать директиву DllImport. Далее по такому шаблону делать импорт:

[DllImport("Name.dll", CharSet = CharSet.Auto)]
public static extern ReturnType FunctionName(Parameters);

Вот и все, основная часть программы написана. Теперь только нужно вызвать хук в конструкторе основной формы, убрать хук, в обработчике закрытия формы.

Установка хука:

Hooks.SetHook();

Уничтожение:

Hooks.UnHook();

Далее по вашему желанию вы можете использовать основную форму для настроек программы (то, что у нас в классе Settings.cs).

Вот что у меня получилось:

 

А вот такая у меня получилась главная форма:

 

Удачи.

Тысленко Макс (Ockonal)
03.12.2008


Автор: Alexander
Дата публикации: 03.12.2008
Число просмотров: 3717

Возврат


Copyright 2007-2009 by Alexander Ignatyev