Skip to content

Commit d41750e

Browse files
committed
Merge branch 'feature/197-speech-hotkeys-rebased' into develop
2 parents 3c7c26a + c10661d commit d41750e

18 files changed

Lines changed: 1067 additions & 62 deletions

ConfigService/Configurations/EDDIConfiguration.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Newtonsoft.Json;
22
using System.Collections.Generic;
3+
using System.Collections.Immutable;
34
using System.Windows;
45

56
namespace EddiConfigService.Configurations
@@ -43,6 +44,14 @@ public class EDDIConfiguration : Config
4344
[JsonProperty("MainWindowPosition")]
4445
public Rect MainWindowPosition { get; set; }
4546

47+
// Hotkeys
48+
49+
[JsonProperty( "Hotkeys" )]
50+
private ImmutableDictionary<string, string> Hotkeys = ImmutableDictionary<string, string>.Empty;
51+
public ImmutableDictionary<string, string> GetHotkeysCopy () => Hotkeys;
52+
public void AddHotkey ( string name, string gesture ) => Hotkeys = Hotkeys.Add( name, gesture );
53+
public void RemoveHotkey ( string name ) => Hotkeys = Hotkeys.Remove( name );
54+
4655
// Default
4756
public EDDIConfiguration()
4857
{

EddiCore/EDDI.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using EddiCompanionAppService;
22
using EddiConfigService;
3+
using EddiCore.Hotkeys;
34
using EddiDataDefinitions;
45
using EddiDataProviderService;
56
using EddiEvents;
@@ -165,6 +166,8 @@ public static EDDI Instance
165166
private static readonly object responderLock = new object();
166167

167168
public DataProviderService DataProvider { get; internal set; } = new DataProviderService();
169+
public HotkeyManager HotkeyManager { get; set; } = new HotkeyManager();
170+
168171

169172
// Information obtained from the configuration
170173
[CanBeNull]

EddiCore/Hotkeys/HotkeyAction.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using System.Windows.Input;
3+
4+
namespace EddiCore.Hotkeys
5+
{
6+
public class HotkeyAction
7+
{
8+
public int? id { get; set; }
9+
public string Name { get; set; }
10+
public string DisplayName { get; set; }
11+
public Action Action { get; set; }
12+
public KeyGesture KeyGesture { get; set; }
13+
14+
public HotkeyAction ( string name, string displayName, Action action, KeyGesture keyGesture = null )
15+
{
16+
Name = name;
17+
DisplayName = displayName;
18+
Action = action;
19+
KeyGesture = keyGesture;
20+
}
21+
}
22+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Windows.Input;
4+
5+
namespace EddiCore.Hotkeys
6+
{
7+
public class HotkeyActionCollection
8+
{
9+
public HotkeyActionCollection ( List<HotkeyAction> hotkeyActions )
10+
{
11+
HotkeyActions = hotkeyActions;
12+
}
13+
14+
public readonly List<HotkeyAction> HotkeyActions;
15+
16+
public void AddGesture ( string name, KeyGesture gesture, int? id = null )
17+
{
18+
var action = HotkeyActions.FirstOrDefault( a => a.Name == name );
19+
if ( action != null )
20+
{
21+
action.KeyGesture = gesture;
22+
action.id = id;
23+
return;
24+
}
25+
26+
throw new KeyNotFoundException( $"The key name '{name}' was not found in the action list." );
27+
}
28+
29+
public void AddId ( string name, int id )
30+
{
31+
var action = HotkeyActions.FirstOrDefault( a => a.Name == name );
32+
if ( action != null )
33+
{
34+
action.id = id;
35+
return;
36+
}
37+
38+
throw new KeyNotFoundException( $"The key name '{name}' was not found in the action list." );
39+
}
40+
41+
public void ClearAllKeyGestures ()
42+
{
43+
foreach ( var action in HotkeyActions )
44+
{
45+
RemoveKeyGestures( action.Name );
46+
}
47+
}
48+
49+
public bool IsKeyGestureAssigned ( string name, Key key, ModifierKeys modifiers )
50+
{
51+
var action = HotkeyActions.FirstOrDefault( a => a.Name == name );
52+
if ( action != null )
53+
{
54+
return HotkeyActions.Any( a =>
55+
a.Name != name &&
56+
a.KeyGesture != null &&
57+
a.KeyGesture.Key == key &&
58+
a.KeyGesture.Modifiers == modifiers );
59+
}
60+
61+
throw new KeyNotFoundException( $"The key name '{name}' was not found in the action list." );
62+
}
63+
64+
public void RemoveKeyGestures ( string name )
65+
{
66+
var action = HotkeyActions.FirstOrDefault( a => a.Name == name );
67+
if ( action != null )
68+
{
69+
action.id = null;
70+
action.KeyGesture = null;
71+
return;
72+
}
73+
74+
throw new KeyNotFoundException( $"The key name '{name}' was not found in the action list." );
75+
}
76+
77+
public bool TryGetValue ( string name, out HotkeyAction action )
78+
{
79+
action = HotkeyActions.FirstOrDefault( a => a.Name == name );
80+
return action != null;
81+
}
82+
83+
public bool TryGetValue ( int id, out HotkeyAction action )
84+
{
85+
action = HotkeyActions.FirstOrDefault( a => a.id != null && a.id == id );
86+
return action != null;
87+
}
88+
}
89+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.Windows.Input;
2+
3+
namespace EddiCore.Hotkeys
4+
{
5+
public static class HotkeyConverter
6+
{
7+
private static readonly KeyGestureConverter keyGestureConverter = new KeyGestureConverter();
8+
9+
public static KeyGesture FromString ( string keyGestureStr )
10+
{
11+
return keyGestureConverter.ConvertFromString( keyGestureStr ) as KeyGesture;
12+
}
13+
14+
public static string ToString ( KeyGesture keyGesture )
15+
{
16+
return keyGestureConverter.ConvertToString( keyGesture );
17+
}
18+
}
19+
}

EddiCore/Hotkeys/HotkeyManager.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using EddiSpeechService;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Runtime.CompilerServices;
5+
using System.Windows.Input;
6+
7+
[assembly: InternalsVisibleTo( "Tests" )]
8+
namespace EddiCore.Hotkeys
9+
{
10+
public class HotkeyManager
11+
{
12+
private static readonly List<HotkeyAction> HotkeyActions = new List<HotkeyAction>
13+
{
14+
new HotkeyAction( "EnableEventResponses", Properties.Resources.hotkey_definition_enable_speech, () =>
15+
{
16+
EDDI.Instance.State[ "speechresponder_quiet" ] = false;
17+
} ),
18+
new HotkeyAction( "DisableEventResponses", Properties.Resources.hotkey_definition_disable_speech, () =>
19+
{
20+
EDDI.Instance.State[ "speechresponder_quiet" ] = true;
21+
SpeechService.Instance.ShutUp();
22+
} ),
23+
new HotkeyAction( "Shutup", Properties.Resources.hotkey_definition_stop_speech, () =>
24+
{
25+
SpeechService.Instance.ShutUp();
26+
} )
27+
};
28+
29+
public HotkeyRegistration Hotkeys;
30+
private const int WM_HOTKEY = 0x0312;
31+
32+
public void SetHandle ( IntPtr newHandle )
33+
{
34+
Hotkeys?.UnregisterAll();
35+
Hotkeys = new HotkeyRegistration( newHandle, new HotkeyActionCollection( HotkeyActions ) );
36+
Hotkeys.RegisterAll();
37+
}
38+
39+
public IntPtr HandleHotkeyMessage ( int msg, IntPtr wParam, ref bool handled )
40+
{
41+
if ( msg == WM_HOTKEY )
42+
{
43+
var id = wParam.ToInt32();
44+
if ( Hotkeys.Collection?.TryGetValue( id, out var hotkeyAction ) ?? false )
45+
{
46+
hotkeyAction.Action.Invoke();
47+
handled = true;
48+
}
49+
}
50+
51+
return IntPtr.Zero;
52+
}
53+
54+
public void RegisterHotkey ( string name, KeyGesture keyGesture ) => Hotkeys.RegisterHotkey( name, keyGesture );
55+
56+
public void UnregisterHotkey ( string name ) => Hotkeys.UnregisterHotkey( name );
57+
58+
public void UnregisterAllHotkeys () => Hotkeys.UnregisterAll();
59+
}
60+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
using EddiConfigService;
2+
using System;
3+
using System.Runtime.InteropServices;
4+
using System.Windows.Input;
5+
using Utilities;
6+
7+
namespace EddiCore.Hotkeys
8+
{
9+
public class HotkeyRegistration
10+
{
11+
public HotkeyActionCollection Collection { get; }
12+
13+
private int hotkeyIdCounter;
14+
private readonly IntPtr handle;
15+
16+
public HotkeyRegistration ( IntPtr handle, HotkeyActionCollection collection )
17+
{
18+
this.handle = handle;
19+
Collection = collection;
20+
}
21+
22+
[DllImport( "user32.dll" )]
23+
private static extern bool RegisterHotKey ( IntPtr hWnd, int id, uint fsModifiers, uint vk );
24+
25+
internal void RegisterAll ()
26+
{
27+
foreach ( var configuredHotkey in ConfigService.Instance.eddiConfiguration.GetHotkeysCopy() )
28+
{
29+
if ( Collection.TryGetValue( configuredHotkey.Key, out var hotkeyAction ) )
30+
{
31+
RegisterHotkey( hotkeyAction.Name, HotkeyConverter.FromString( configuredHotkey.Value ) );
32+
}
33+
}
34+
}
35+
36+
public void RegisterHotkey ( string name, KeyGesture keyGesture )
37+
{
38+
try
39+
{
40+
if ( TryRegisterHotkey( name, keyGesture, out var id ) )
41+
{
42+
Collection.AddGesture( name, keyGesture, id );
43+
ConfigService.Instance.eddiConfiguration.AddHotkey( name, HotkeyConverter.ToString( keyGesture ) );
44+
}
45+
else
46+
{
47+
throw new InvalidOperationException( "Hotkey registration failed." );
48+
}
49+
}
50+
catch ( Exception e )
51+
{
52+
Logging.Error(e.Message, e);
53+
}
54+
}
55+
56+
private bool TryRegisterHotkey ( string name, KeyGesture keyGesture, out int id )
57+
{
58+
if ( string.IsNullOrWhiteSpace( name ) )
59+
{
60+
throw new ArgumentNullException( nameof( name ) );
61+
}
62+
63+
if ( keyGesture == null )
64+
{
65+
throw new ArgumentNullException( nameof( keyGesture ) );
66+
}
67+
68+
// Unregister previous hotkey for this name, if any
69+
TryUnregisterHotkey( name );
70+
71+
// Register a new hotkey
72+
var modifiers = (uint)keyGesture.Modifiers;
73+
var key = (uint)KeyInterop.VirtualKeyFromKey( keyGesture.Key );
74+
id = hotkeyIdCounter++;
75+
return RegisterHotKey( handle, id, modifiers, key );
76+
}
77+
78+
private bool TryUnregisterHotkey ( string name )
79+
{
80+
// Unregister previous hotkey for this name, if any
81+
if ( Collection.TryGetValue( name, out var hotkeyAction ) && hotkeyAction.id is int oldId )
82+
{
83+
return UnregisterHotKey( handle, oldId );
84+
}
85+
86+
return false;
87+
}
88+
89+
[DllImport( "user32.dll" )]
90+
private static extern bool UnregisterHotKey ( IntPtr hWnd, int id );
91+
92+
public void UnregisterHotkey ( string name )
93+
{
94+
if ( TryUnregisterHotkey( name ) )
95+
{
96+
Collection.RemoveKeyGestures( name );
97+
ConfigService.Instance.eddiConfiguration.RemoveHotkey( name );
98+
}
99+
}
100+
101+
internal void UnregisterAll ()
102+
{
103+
foreach ( var hotkeyAction in Collection.HotkeyActions )
104+
{
105+
if ( hotkeyAction.id is int id )
106+
{
107+
UnregisterHotKey( handle, id );
108+
}
109+
}
110+
111+
Collection.ClearAllKeyGestures();
112+
}
113+
}
114+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<Window x:Class="EddiCore.Hotkeys.HotkeysWindow"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6+
xmlns:resx="clr-namespace:EddiCore.Properties"
7+
mc:Ignorable="d"
8+
Title="{x:Static resx:Resources.hotkey_window_title}"
9+
FocusManager.FocusedElement="{Binding ElementName=actionComboBox}"
10+
WindowStartupLocation="CenterOwner"
11+
WindowStyle="ToolWindow"
12+
KeyDown="HotkeysWindow_KeyDown"
13+
KeyUp="HotkeysWindow_KeyUp"
14+
>
15+
<StackPanel Orientation="Vertical" Margin="5">
16+
<StackPanel Orientation="Horizontal" >
17+
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{x:Static resx:Resources.hotkey_action_label}" MinWidth="42" Margin="5" />
18+
<ComboBox x:Name="actionComboBox" MinWidth="273" SelectionChanged="ActionComboBoxOnSelectionChanged" Margin="5"/>
19+
</StackPanel>
20+
<StackPanel Orientation="Horizontal">
21+
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{x:Static resx:Resources.hotkey_hotkey_label}" MinWidth="42" Margin="5" />
22+
<Border BorderThickness="0.5" Margin="5,5,5,5" BorderBrush="Black" Background="DarkGray">
23+
<TextBlock x:Name="hotkeyTextBlock"
24+
Text="{x:Static resx:Resources.hotkey_input_prompt}"
25+
HorizontalAlignment="Center" TextAlignment="Center" VerticalAlignment="Center"
26+
Background="WhiteSmoke"
27+
Margin="1" MinWidth="200" TextWrapping="Wrap" />
28+
</Border>
29+
<Button VerticalAlignment="Center"
30+
Content="{x:Static resx:Resources.hotkey_clear_button}"
31+
Click="ClearHotkeyButton_Click"
32+
Margin="5" MinWidth ="60" />
33+
</StackPanel>
34+
<UniformGrid Rows="1" Columns="2" HorizontalAlignment="Center" Margin="5, 0" >
35+
<Button IsDefault="True"
36+
Content="{x:Static resx:Resources.hotkey_ok_button}"
37+
VerticalAlignment="Top"
38+
Click="acceptButtonClick"
39+
Margin="5" MinWidth ="60"/>
40+
<Button IsCancel="True"
41+
Content="{x:Static resx:Resources.hotkey_cancel_button}"
42+
VerticalAlignment="Top"
43+
Click="cancelButtonClick"
44+
Margin="5" MinWidth ="60"/>
45+
</UniformGrid>
46+
</StackPanel>
47+
</Window>

0 commit comments

Comments
 (0)