using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace EpisodeRenamer
{
///
/// Класс для обработки горячих клавиш с использованием Task
///
public class HotkeyListener : IDisposable
{
#region Windows API Imports
[DllImport("user32.dll")]
private static extern short GetAsyncKeyState(int vKey);
[DllImport("kernel32.dll")]
private static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
#endregion
#region Key Codes
public static class VirtualKeyCodes
{
public const int VK_CONTROL = 0x11;
public const int VK_ALT = 0x12;
public const int VK_SHIFT = 0x10;
public const int VK_R = 0x52;
public const int VK_ESCAPE = 0x1B;
// Добавьте другие коды клавиш по необходимости
}
#endregion
#region Events and Delegates
///
/// Делегат для события нажатия комбинации клавиш
///
public delegate void HotkeyEventHandler(string combinationName);
///
/// Событие, возникающее при нажатии зарегистрированной комбинации клавиш
///
public event HotkeyEventHandler OnHotkeyPressed;
///
/// Событие, возникающее при ошибке в работе слушателя
///
public event Action OnError;
#endregion
#region Private Fields
private readonly List _keyCombinations;
private CancellationTokenSource _cancellationTokenSource;
private Task _listenerTask;
private bool _isDisposed;
private readonly object _lockObject = new object();
private bool _isRunning;
private IntPtr consoleHandle = Process.GetCurrentProcess().MainWindowHandle;
#endregion
#region Key Combination Class
///
/// Класс для описания комбинации клавиш
///
private class KeyCombination
{
public string Name { get; set; }
public int[] KeyCodes { get; set; }
public int CooldownMs { get; set; } = 300;
public DateTime LastTriggered { get; set; } = DateTime.MinValue;
}
#endregion
#region Constructor
///
/// Создает новый экземпляр слушателя горячих клавиш
///
public HotkeyListener()
{
_keyCombinations = new List();
_cancellationTokenSource = new CancellationTokenSource();
}
#endregion
#region Public Methods
///
/// Регистрирует новую комбинацию клавиш для отслеживания
///
/// Имя комбинации
/// Коды клавиш (все должны быть нажаты одновременно)
/// Время задержки между срабатываниями (мс)
public void RegisterCombination(string name, int[] keyCodes, int cooldownMs = 300)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentException("Имя комбинации не может быть пустым", nameof(name));
if (keyCodes == null || keyCodes.Length == 0)
throw new ArgumentException("Массив кодов клавиш не может быть пустым", nameof(keyCodes));
lock (_lockObject)
{
var existing = _keyCombinations.Find(k => k.Name == name);
if (existing != null)
{
_keyCombinations.Remove(existing);
}
_keyCombinations.Add(new KeyCombination
{
Name = name,
KeyCodes = keyCodes,
CooldownMs = cooldownMs
});
}
}
///
/// Удаляет комбинацию клавиш из отслеживания
///
/// Имя комбинации
public void UnregisterCombination(string name)
{
lock (_lockObject)
{
var combination = _keyCombinations.Find(k => k.Name == name);
if (combination != null)
{
_keyCombinations.Remove(combination);
}
}
}
///
/// Запускает прослушивание горячих клавиш
///
public async Task StartListeningAsync()
{
if (_isRunning)
return;
lock (_lockObject)
{
if (_isRunning)
return;
_cancellationTokenSource = new CancellationTokenSource();
_isRunning = true;
}
try
{
_listenerTask = Task.Run(() => ListenForHotkeys(_cancellationTokenSource.Token),
_cancellationTokenSource.Token);
await Task.CompletedTask;
}
catch (Exception ex)
{
OnError?.Invoke(ex);
throw;
}
}
///
/// Останавливает прослушивание горячих клавиш
///
public async Task StopListeningAsync()
{
if (!_isRunning)
return;
lock (_lockObject)
{
if (!_isRunning)
return;
_isRunning = false;
}
try
{
_cancellationTokenSource?.Cancel();
if (_listenerTask != null && !_listenerTask.IsCompleted)
{
await _listenerTask.ContinueWith(t =>
{
// Игнорируем исключения отмены задачи
if (t.IsFaulted)
OnError?.Invoke(t.Exception);
});
}
}
catch (OperationCanceledException)
{
// Ожидаемое исключение при отмене
}
catch (Exception ex)
{
OnError?.Invoke(ex);
throw;
}
}
///
/// Получает статус работы слушателя
///
public bool IsListening => _isRunning;
///
/// Получает список зарегистрированных комбинаций
///
public IReadOnlyList RegisteredCombinations
{
get
{
lock (_lockObject)
{
return _keyCombinations.ConvertAll(k => k.Name).AsReadOnly();
}
}
}
#endregion
#region Private Methods
private async Task ListenForHotkeys(CancellationToken cancellationToken)
{
try
{
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(10, cancellationToken); // Небольшая задержка для снижения нагрузки
lock (_lockObject)
{
foreach (var combination in _keyCombinations)
{
if (IsCombinationPressed(combination.KeyCodes))
{
// Проверяем cooldown
if ((DateTime.Now - combination.LastTriggered).TotalMilliseconds >= combination.CooldownMs)
{
combination.LastTriggered = DateTime.Now;
TriggerHotkeyEvent(combination.Name);
}
}
}
}
}
}
catch (OperationCanceledException)
{
// Ожидаемое исключение при отмене
}
catch (Exception ex)
{
OnError?.Invoke(ex);
}
}
private bool IsCombinationPressed(int[] keyCodes)
{
foreach (var keyCode in keyCodes)
{
if (!IsKeyPressed(keyCode))
return false;
}
return true;
}
private bool IsKeyPressed(int keyCode)
{
return (GetAsyncKeyState(keyCode) & 0x8000) != 0;
}
private void TriggerHotkeyEvent(string combinationName)
{
if (GetForegroundWindow() != consoleHandle)
return;
try
{
OnHotkeyPressed?.Invoke(combinationName);
}
catch (Exception ex)
{
OnError?.Invoke(new InvalidOperationException(
$"Ошибка при обработке события для комбинации {combinationName}", ex));
}
}
#endregion
#region IDisposable Implementation
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
if (disposing)
{
StopListeningAsync().GetAwaiter().GetResult();
_cancellationTokenSource?.Dispose();
}
_isDisposed = true;
}
}
~HotkeyListener()
{
Dispose(false);
}
#endregion
}
}