Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hotkey picker #65

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 73 additions & 72 deletions src/SoulSplitter/Hotkeys/GlobalHotKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,99 +22,100 @@
using System.Windows.Input;
using SoulSplitter.Native;

namespace SoulSplitter.Hotkeys;

public static class GlobalHotKey
namespace SoulSplitter.Hotkeys
{
private static readonly List<(int id, ModifierKeys modifier, Key key, Action action)> Hotkeys = [];
private static readonly ManualResetEvent WindowReadyEvent = new(false);
public static volatile HotkeyForm? HotkeyForm;
public static volatile IntPtr Handle;
private static int _currentId;

static GlobalHotKey()
public static class GlobalHotKey
{
var messageLoopThread = new Thread(delegate ()
{
Application.Run(new HotkeyForm(WindowReadyEvent));
});
messageLoopThread.Name = "MessageLoopThread";
messageLoopThread.IsBackground = true;
messageLoopThread.Start();
}
private static readonly List<(int id, Hotkey hotkey, Action action)> Hotkeys = new List<(int, Hotkey, Action)>();

Check notice on line 29 in src/SoulSplitter/Hotkeys/GlobalHotKey.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Use preferred style of 'new' expression when created type is evident

Redundant type specification
private static readonly ManualResetEvent WindowReadyEvent = new ManualResetEvent(false);

Check notice on line 30 in src/SoulSplitter/Hotkeys/GlobalHotKey.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Use preferred style of 'new' expression when created type is evident

Redundant type specification
public static volatile HotkeyForm HotkeyForm = null!;
public static volatile IntPtr Handle;
private static int _currentId;

public static void OnHotkeyPressed(ModifierKeys modifier, Key key)
{
Hotkeys.ForEach(x =>
static GlobalHotKey()
{
if (modifier == x.modifier && key == x.key)
var messageLoopThread = new Thread(delegate ()
{
x.action();
}
});
}

public static int RegisterHotKey(ModifierKeys modifier, Key key, Action action)
{
WindowReadyEvent.WaitOne(); //wait for hotkey window to have initialized
Application.Run(new HotkeyForm(WindowReadyEvent));
});
messageLoopThread.Name = "MessageLoopThread";
messageLoopThread.IsBackground = true;
messageLoopThread.Start();
}

var virtualKeyCode = (Keys)KeyInterop.VirtualKeyFromKey(key);
int id = Interlocked.Increment(ref _currentId);
public static void OnHotkeyPressed(Hotkey hotkey)
{
Hotkeys.ForEach(x =>
{
if (hotkey.Modifiers == x.hotkey.Modifiers && hotkey.Key == x.hotkey.Key)
{
x.action();
}
});
}

Delegate register = (Action)(() =>
public static int RegisterHotKey(Hotkey hotkey, Action action)
{
User32.RegisterHotkey(
Handle,
id,
(uint)modifier | 0x4000, //no repeat
(uint)virtualKeyCode);
});
WindowReadyEvent.WaitOne(); //wait for hotkey window to have initialized

HotkeyForm?.Invoke(register);
Hotkeys.Add((id, modifier, key, action));
return id;
}
var virtualKeyCode = (Keys)KeyInterop.VirtualKeyFromKey(hotkey.Key);
int id = Interlocked.Increment(ref _currentId);

public static void UnregisterHotKey(int id)
{
if (Hotkeys.All(i => i.id != id))
{
return;
Delegate register = (Action)(() =>
{
User32.RegisterHotkey(
Handle,
id,
(uint)hotkey.Modifiers | 0x4000, //no repeat
(uint)virtualKeyCode);
});

HotkeyForm.Invoke(register);
Hotkeys.Add((id, hotkey, action));
return id;
}

Delegate unregister = (Action)(() =>
public static void UnregisterHotKey(int id)
{
User32.UnregisterHotkey(Handle, id);
});
if (Hotkeys.All(i => i.id != id))
{
return;
}

HotkeyForm?.Invoke(unregister);
Hotkeys.Remove(Hotkeys.Find(i => i.id == id));
}
}
Delegate unregister = (Action)(() =>
{
User32.UnregisterHotkey(Handle, id);
});

public class HotkeyForm : Form
{
public HotkeyForm(ManualResetEvent windowReadyEvent)
{
GlobalHotKey.Handle = Handle;
GlobalHotKey.HotkeyForm = this;
windowReadyEvent.Set();
HotkeyForm.Invoke(unregister);
Hotkeys.Remove(Hotkeys.Find(i => i.id == id));
}
}

protected override void WndProc(ref Message windowMessage)
public class HotkeyForm : Form
{
base.WndProc(ref windowMessage);
public HotkeyForm(ManualResetEvent windowReadyEvent)
{
GlobalHotKey.Handle = Handle;
GlobalHotKey.HotkeyForm = this;
windowReadyEvent.Set();
}

if (windowMessage.Msg == 0x0312) //0x0312 is the hotkey message
protected override void WndProc(ref Message windowMessage)
{
var key = KeyInterop.KeyFromVirtualKey((int)windowMessage.LParam >> 16 & 0xFFFF);
var modifier = (ModifierKeys)((int)windowMessage.LParam & 0xFFFF);
GlobalHotKey.OnHotkeyPressed(modifier, key);
base.WndProc(ref windowMessage);

if (windowMessage.Msg == 0x0312) //0x0312 is the hotkey message
{
var key = KeyInterop.KeyFromVirtualKey((int)windowMessage.LParam >> 16 & 0xFFFF);
var modifier = (ModifierKeys)((int)windowMessage.LParam & 0xFFFF);
GlobalHotKey.OnHotkeyPressed(new Hotkey(){Modifiers = modifier, Key = key});

Check notice on line 112 in src/SoulSplitter/Hotkeys/GlobalHotKey.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Redundant empty argument list on object creation expression

Empty argument list is redundant
}
}
}

protected override void SetVisibleCore(bool value)
{
base.SetVisibleCore(false);
protected override void SetVisibleCore(bool value)
{
base.SetVisibleCore(false);
}
}
}
31 changes: 31 additions & 0 deletions src/SoulSplitter/Hotkeys/Hotkey.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// This file is part of the SoulSplitter distribution (https://github.com/FrankvdStam/SoulSplitter).
// Copyright (c) 2022 Frank van der Stam.
// https://github.com/FrankvdStam/SoulSplitter/blob/main/LICENSE
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

using System;

Check warning on line 17 in src/SoulSplitter/Hotkeys/Hotkey.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Redundant using directive

Using directive is not required by the code and can be safely removed

Check warning

Code scanning / QDNET

Redundant using directive Warning

Using directive is not required by the code and can be safely removed
using System.Collections.Generic;

Check warning on line 18 in src/SoulSplitter/Hotkeys/Hotkey.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Redundant using directive

Using directive is not required by the code and can be safely removed

Check warning

Code scanning / QDNET

Redundant using directive Warning

Using directive is not required by the code and can be safely removed
using System.Linq;

Check warning on line 19 in src/SoulSplitter/Hotkeys/Hotkey.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Redundant using directive

Using directive is not required by the code and can be safely removed

Check warning

Code scanning / QDNET

Redundant using directive Warning

Using directive is not required by the code and can be safely removed
using System.Text;

Check warning on line 20 in src/SoulSplitter/Hotkeys/Hotkey.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Redundant using directive

Using directive is not required by the code and can be safely removed

Check warning

Code scanning / QDNET

Redundant using directive Warning

Using directive is not required by the code and can be safely removed
using System.Threading.Tasks;

Check warning on line 21 in src/SoulSplitter/Hotkeys/Hotkey.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Redundant using directive

Using directive is not required by the code and can be safely removed

Check warning

Code scanning / QDNET

Redundant using directive Warning

Using directive is not required by the code and can be safely removed
using System.Windows.Input;

namespace SoulSplitter.Hotkeys
{
public class Hotkey
{
public ModifierKeys Modifiers { get; set; }
public Key Key { get; set; }
}
}
59 changes: 59 additions & 0 deletions src/SoulSplitter/UI/Converters/HotkeyToStringConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// This file is part of the SoulSplitter distribution (https://github.com/FrankvdStam/SoulSplitter).
// Copyright (c) 2022 Frank van der Stam.
// https://github.com/FrankvdStam/SoulSplitter/blob/main/LICENSE
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

using SoulSplitter.Hotkeys;
using SoulSplitter.UI.EldenRing;

Check warning on line 18 in src/SoulSplitter/UI/Converters/HotkeyToStringConverter.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Redundant using directive

Using directive is not required by the code and can be safely removed

Check warning

Code scanning / QDNET

Redundant using directive Warning

Using directive is not required by the code and can be safely removed
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

Check warning on line 24 in src/SoulSplitter/UI/Converters/HotkeyToStringConverter.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Redundant using directive

Using directive is not required by the code and can be safely removed

Check warning

Code scanning / QDNET

Redundant using directive Warning

Using directive is not required by the code and can be safely removed
using System.Windows.Data;
using System.Windows.Input;

namespace SoulSplitter.UI.Converters
{
public class HotkeyToStringConverter : IValueConverter
{
private static List<ModifierKeys> _modifiers = Enum.GetValues(typeof(ModifierKeys)).Cast<ModifierKeys>().Where(i => i != ModifierKeys.None).ToList();

Check notice on line 32 in src/SoulSplitter/UI/Converters/HotkeyToStringConverter.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Field can be made readonly (private accessibility)

Field can be made readonly

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)

Check warning on line 34 in src/SoulSplitter/UI/Converters/HotkeyToStringConverter.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Nullability conflicts with annotations in hierarchy

Nullability of a member conflicts with annotations in hierarchy

Check warning on line 34 in src/SoulSplitter/UI/Converters/HotkeyToStringConverter.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Nullability conflicts with annotations in hierarchy

Nullability of a member conflicts with annotations in hierarchy

Check warning

Code scanning / QDNET

Nullability conflicts with annotations in hierarchy Warning

Nullability of a member conflicts with annotations in hierarchy

Check warning

Code scanning / QDNET

Nullability conflicts with annotations in hierarchy Warning

Nullability of a member conflicts with annotations in hierarchy
{
if (value is Hotkey hotkey)

Check notice on line 36 in src/SoulSplitter/UI/Converters/HotkeyToStringConverter.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Invert 'if' statement to reduce nesting

Invert 'if' statement to reduce nesting
{
var sb = new StringBuilder();
foreach (var modifier in _modifiers)

Check notice on line 39 in src/SoulSplitter/UI/Converters/HotkeyToStringConverter.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Part of foreach loop can be converted into LINQ-expression but another 'GetEnumerator' method will be used

Part of loop's body can be converted into LINQ-expression but another 'GetEnumerator' method will be used
{
if (hotkey.Modifiers.HasFlag(modifier))

Check notice on line 41 in src/SoulSplitter/UI/Converters/HotkeyToStringConverter.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Invert 'if' statement to reduce nesting

Invert 'if' statement to reduce nesting
{
sb.Append(modifier);
sb.Append(" + ");
}
}
sb.Append(hotkey.Key);
return sb.ToString();
}

throw new ArgumentException($"{value} is not a {nameof(Hotkey)}", nameof(value));
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)

Check warning on line 54 in src/SoulSplitter/UI/Converters/HotkeyToStringConverter.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Nullability conflicts with annotations in hierarchy

Nullability of a member conflicts with annotations in hierarchy

Check warning on line 54 in src/SoulSplitter/UI/Converters/HotkeyToStringConverter.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Nullability conflicts with annotations in hierarchy

Nullability of a member conflicts with annotations in hierarchy

Check warning

Code scanning / QDNET

Nullability conflicts with annotations in hierarchy Warning

Nullability of a member conflicts with annotations in hierarchy

Check warning

Code scanning / QDNET

Nullability conflicts with annotations in hierarchy Warning

Nullability of a member conflicts with annotations in hierarchy
{
return "";
}
}
}
Loading
Loading