Skip to content

Commit 8c568f0

Browse files
XTorLukas0x5bfayaira2
authored
Fix: Fixed an issue where it didn't work to map certain keys to Actions (#15420)
Co-authored-by: 0x5bfa <62196528+0x5bfa@users.noreply.github.com> Co-authored-by: Yair <39923744+yaira2@users.noreply.github.com>
1 parent db08325 commit 8c568f0

File tree

10 files changed

+246
-80
lines changed

10 files changed

+246
-80
lines changed

src/Files.App/Data/Commands/HotKey/HotKey.cs

Lines changed: 110 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -81,60 +81,46 @@ namespace Files.App.Data.Commands
8181
[Keys.Number7] = "7",
8282
[Keys.Number8] = "8",
8383
[Keys.Number9] = "9",
84-
[Keys.Pad0] = GetLocalizedKey("Pad0"),
85-
[Keys.Pad1] = GetLocalizedKey("Pad1"),
86-
[Keys.Pad2] = GetLocalizedKey("Pad2"),
87-
[Keys.Pad3] = GetLocalizedKey("Pad3"),
88-
[Keys.Pad4] = GetLocalizedKey("Pad4"),
89-
[Keys.Pad5] = GetLocalizedKey("Pad5"),
90-
[Keys.Pad6] = GetLocalizedKey("Pad6"),
91-
[Keys.Pad7] = GetLocalizedKey("Pad7"),
92-
[Keys.Pad8] = GetLocalizedKey("Pad8"),
93-
[Keys.Pad9] = GetLocalizedKey("Pad9"),
94-
[Keys.A] = "A",
95-
[Keys.B] = "B",
96-
[Keys.C] = "C",
97-
[Keys.D] = "D",
98-
[Keys.E] = "E",
99-
[Keys.F] = "F",
100-
[Keys.G] = "G",
101-
[Keys.H] = "H",
102-
[Keys.I] = "I",
103-
[Keys.J] = "J",
104-
[Keys.K] = "K",
105-
[Keys.L] = "L",
106-
[Keys.M] = "M",
107-
[Keys.N] = "N",
108-
[Keys.O] = "O",
109-
[Keys.P] = "P",
110-
[Keys.Q] = "Q",
111-
[Keys.R] = "R",
112-
[Keys.S] = "S",
113-
[Keys.T] = "T",
114-
[Keys.U] = "U",
115-
[Keys.V] = "V",
116-
[Keys.W] = "W",
117-
[Keys.X] = "X",
118-
[Keys.Y] = "Y",
119-
[Keys.Z] = "Z",
120-
[Keys.Add] = "+",
121-
[Keys.Subtract] = "-",
122-
[Keys.Multiply] = "*",
123-
[Keys.Divide] = "/",
124-
[Keys.Oem1] = GetKeyCharacter(Forms.Keys.Oem1),
125-
[Keys.Oem2] = GetKeyCharacter(Forms.Keys.Oem2),
126-
[Keys.Oem3] = GetKeyCharacter(Forms.Keys.Oem3),
127-
[Keys.Oem4] = GetKeyCharacter(Forms.Keys.Oem4),
128-
[Keys.Oem5] = GetKeyCharacter(Forms.Keys.Oem5),
129-
[Keys.Oem6] = GetKeyCharacter(Forms.Keys.Oem6),
130-
[Keys.Oem7] = GetKeyCharacter(Forms.Keys.Oem7),
131-
[Keys.Oem8] = GetKeyCharacter(Forms.Keys.Oem8),
132-
[Keys.OemPlus] = GetKeyCharacter(Forms.Keys.Oemplus),
133-
[Keys.OemComma] = GetKeyCharacter(Forms.Keys.Oemcomma),
134-
[Keys.OemMinus] = GetKeyCharacter(Forms.Keys.OemMinus),
135-
[Keys.OemPeriod] = GetKeyCharacter(Forms.Keys.OemPeriod),
136-
[Keys.Oem102] = GetKeyCharacter(Forms.Keys.Oem102),
137-
[Keys.OemClear] = GetKeyCharacter(Forms.Keys.OemClear),
84+
[Keys.A] = GetKeyCharacter(Forms.Keys.A).ToUpper(),
85+
[Keys.B] = GetKeyCharacter(Forms.Keys.B).ToUpper(),
86+
[Keys.C] = GetKeyCharacter(Forms.Keys.C).ToUpper(),
87+
[Keys.D] = GetKeyCharacter(Forms.Keys.D).ToUpper(),
88+
[Keys.E] = GetKeyCharacter(Forms.Keys.E).ToUpper(),
89+
[Keys.F] = GetKeyCharacter(Forms.Keys.F).ToUpper(),
90+
[Keys.G] = GetKeyCharacter(Forms.Keys.G).ToUpper(),
91+
[Keys.H] = GetKeyCharacter(Forms.Keys.H).ToUpper(),
92+
[Keys.I] = GetKeyCharacter(Forms.Keys.I).ToUpper(),
93+
[Keys.J] = GetKeyCharacter(Forms.Keys.J).ToUpper(),
94+
[Keys.K] = GetKeyCharacter(Forms.Keys.K).ToUpper(),
95+
[Keys.L] = GetKeyCharacter(Forms.Keys.L).ToUpper(),
96+
[Keys.M] = GetKeyCharacter(Forms.Keys.M).ToUpper(),
97+
[Keys.N] = GetKeyCharacter(Forms.Keys.N).ToUpper(),
98+
[Keys.O] = GetKeyCharacter(Forms.Keys.O).ToUpper(),
99+
[Keys.P] = GetKeyCharacter(Forms.Keys.P).ToUpper(),
100+
[Keys.Q] = GetKeyCharacter(Forms.Keys.Q).ToUpper(),
101+
[Keys.R] = GetKeyCharacter(Forms.Keys.R).ToUpper(),
102+
[Keys.S] = GetKeyCharacter(Forms.Keys.S).ToUpper(),
103+
[Keys.T] = GetKeyCharacter(Forms.Keys.T).ToUpper(),
104+
[Keys.U] = GetKeyCharacter(Forms.Keys.U).ToUpper(),
105+
[Keys.V] = GetKeyCharacter(Forms.Keys.V).ToUpper(),
106+
[Keys.W] = GetKeyCharacter(Forms.Keys.W).ToUpper(),
107+
[Keys.X] = GetKeyCharacter(Forms.Keys.X).ToUpper(),
108+
[Keys.Y] = GetKeyCharacter(Forms.Keys.Y).ToUpper(),
109+
[Keys.Z] = GetKeyCharacter(Forms.Keys.Z).ToUpper(),
110+
[Keys.Oem1] = GetKeyCharacter(Forms.Keys.Oem1).ToUpper(),
111+
[Keys.Oem2] = GetKeyCharacter(Forms.Keys.Oem2).ToUpper(),
112+
[Keys.Oem3] = GetKeyCharacter(Forms.Keys.Oem3).ToUpper(),
113+
[Keys.Oem4] = GetKeyCharacter(Forms.Keys.Oem4).ToUpper(),
114+
[Keys.Oem5] = GetKeyCharacter(Forms.Keys.Oem5).ToUpper(),
115+
[Keys.Oem6] = GetKeyCharacter(Forms.Keys.Oem6).ToUpper(),
116+
[Keys.Oem7] = GetKeyCharacter(Forms.Keys.Oem7).ToUpper(),
117+
[Keys.Oem8] = GetKeyCharacter(Forms.Keys.Oem8).ToUpper(),
118+
[Keys.Oem102] = GetKeyCharacter(Forms.Keys.Oem102).ToUpper(),
119+
[Keys.OemPlus] = GetKeyCharacter(Forms.Keys.Oemplus).ToUpper(),
120+
[Keys.OemComma] = GetKeyCharacter(Forms.Keys.Oemcomma).ToUpper(),
121+
[Keys.OemMinus] = GetKeyCharacter(Forms.Keys.OemMinus).ToUpper(),
122+
[Keys.OemPeriod] = GetKeyCharacter(Forms.Keys.OemPeriod).ToUpper(),
123+
[Keys.OemClear] = GetKeyCharacter(Forms.Keys.OemClear).ToUpper(),
138124
[Keys.Application] = GetLocalizedKey("Application"),
139125
[Keys.Application1] = GetLocalizedKey("Application1"),
140126
[Keys.Application2] = GetLocalizedKey("Application2"),
@@ -154,6 +140,23 @@ namespace Files.App.Data.Commands
154140
[Keys.Mute] = GetLocalizedKey("MediaMute"),
155141
[Keys.VolumeDown] = GetLocalizedKey("MediaVolumeDown"),
156142
[Keys.VolumeUp] = GetLocalizedKey("MediaVolumeUp"),
143+
144+
// NumPad Keys
145+
[Keys.Add] = GetLocalizedNumPadKey("+"),
146+
[Keys.Subtract] = GetLocalizedNumPadKey("-"),
147+
[Keys.Multiply] = GetLocalizedNumPadKey("*"),
148+
[Keys.Divide] = GetLocalizedNumPadKey("/"),
149+
[Keys.Decimal] = GetLocalizedNumPadKey("."),
150+
[Keys.Pad0] = GetLocalizedNumPadKey("0"),
151+
[Keys.Pad1] = GetLocalizedNumPadKey("1"),
152+
[Keys.Pad2] = GetLocalizedNumPadKey("2"),
153+
[Keys.Pad3] = GetLocalizedNumPadKey("3"),
154+
[Keys.Pad4] = GetLocalizedNumPadKey("4"),
155+
[Keys.Pad5] = GetLocalizedNumPadKey("5"),
156+
[Keys.Pad6] = GetLocalizedNumPadKey("6"),
157+
[Keys.Pad7] = GetLocalizedNumPadKey("7"),
158+
[Keys.Pad8] = GetLocalizedNumPadKey("8"),
159+
[Keys.Pad9] = GetLocalizedNumPadKey("9"),
157160
}.ToFrozenDictionary();
158161

159162
/// <summary>
@@ -278,9 +281,39 @@ public static HotKey Parse(string code, bool localized = true)
278281
var key = Keys.None;
279282
var modifier = KeyModifiers.None;
280283
bool isVisible = true;
284+
var splitCharacter = '+';
281285

286+
// Remove leading and trailing whitespace from the code string
282287
code = code.Trim();
283-
var parts = code.Split('+').Select(part => part.Trim());
288+
289+
// Split the code by "++" into a list of parts
290+
List<string> parts = [.. code.Split(new string(splitCharacter, 2), StringSplitOptions.None)];
291+
292+
if (parts.Count == 2)
293+
{
294+
// If there are two parts after splitting by "++", split the first part by "+"
295+
// and append the second part prefixed with a "+"
296+
parts = [.. parts.First().Split(splitCharacter), splitCharacter + parts.Last()];
297+
}
298+
else
299+
{
300+
// Split the code by a single '+' and trim each part
301+
parts = [.. code.Split(splitCharacter)];
302+
303+
// If the resulting list has two parts and one of them is empty, use the original code as the single element
304+
if (parts.Count == 2 && (string.IsNullOrEmpty(parts.First()) || string.IsNullOrEmpty(parts.Last())))
305+
{
306+
parts = [code];
307+
}
308+
else if (parts.Count > 0 && string.IsNullOrEmpty(parts.Last()))
309+
{
310+
// If the last part is empty, remove it and add a "+" to the last non-empty part
311+
parts.RemoveAt(parts.Count - 1);
312+
parts[^1] += splitCharacter;
313+
}
314+
}
315+
316+
parts = [.. parts.Select(part => part.Trim())];
284317

285318
foreach (var part in parts)
286319
{
@@ -330,11 +363,31 @@ private static string GetLocalizedKey(string key)
330363
return $"Key/{key}".GetLocalizedResource();
331364
}
332365

366+
private static string GetLocalizedNumPadKey(string key)
367+
{
368+
return "NumPadTypeName".GetLocalizedResource() + " " + key;
369+
}
370+
333371
private static string GetKeyCharacter(Forms.Keys key)
334372
{
335-
var buffer = new StringBuilder(256);
373+
var buffer = new StringBuilder(4);
336374
var state = new byte[256];
337-
_ = Win32PInvoke.ToUnicode((uint)key, 0, state, buffer, 256, 0);
375+
376+
// Get the current keyboard state
377+
if (!Win32PInvoke.GetKeyboardState(state))
378+
return buffer.ToString();
379+
380+
// Convert the key to its virtual key code
381+
var virtualKey = (uint)key;
382+
383+
// Map the virtual key to a scan code
384+
var scanCode = Win32PInvoke.MapVirtualKey(virtualKey, 0);
385+
386+
// Get the active keyboard layout
387+
var keyboardLayout = Win32PInvoke.GetKeyboardLayout(0);
388+
389+
if (Win32PInvoke.ToUnicodeEx(virtualKey, scanCode, state, buffer, buffer.Capacity, 0, keyboardLayout) > 0)
390+
return buffer[^1].ToString();
338391

339392
return buffer.ToString();
340393
}

src/Files.App/Data/Commands/HotKey/Keys.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ public enum Keys : ushort
105105
Subtract = VirtualKey.Subtract,
106106
Multiply = VirtualKey.Multiply,
107107
Divide = VirtualKey.Divide,
108+
Decimal = VirtualKey.Decimal,
108109
Oem1 = 186,
109110
Oem2 = 191,
110111
Oem3 = 192,

src/Files.App/Data/Items/ModifiableActionItem.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ public bool IsInEditMode
3838
set => SetProperty(ref _IsInEditMode, value);
3939
}
4040

41+
private bool _IsValidKeyBinding;
42+
public bool IsValidKeyBinding
43+
{
44+
get => _IsValidKeyBinding;
45+
set => SetProperty(ref _IsValidKeyBinding, value);
46+
}
47+
4148
private bool _IsDefinedByDefault;
4249
public bool IsDefinedByDefault
4350
{

src/Files.App/Helpers/Win32/Win32PInvoke.Methods.cs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public static extern IntPtr SetWindowLongPtr64(
103103
WindowLongFlags nIndex,
104104
IntPtr dwNewLong
105105
);
106-
106+
107107
[DllImport("shell32.dll")]
108108
public static extern IntPtr SHBrowseForFolder(
109109
ref BROWSEINFO lpbi
@@ -231,6 +231,39 @@ public static extern int ToUnicode(
231231
uint flags
232232
);
233233

234+
[DllImport("user32.dll", CharSet = CharSet.Auto)]
235+
public static extern int ToUnicodeEx(
236+
uint virtualKeyCode,
237+
uint scanCode,
238+
byte[] keyboardState,
239+
[Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder receivingBuffer,
240+
int bufferSize,
241+
uint flags,
242+
IntPtr keyboardLayout
243+
);
244+
245+
[DllImport("user32.dll")]
246+
public static extern bool GetKeyboardState(
247+
byte[] lpKeyState
248+
);
249+
250+
[DllImport("user32.dll", CharSet = CharSet.Auto)]
251+
public static extern IntPtr GetKeyboardLayout
252+
(
253+
uint idThread
254+
);
255+
256+
[DllImport("user32.dll")]
257+
public static extern uint MapVirtualKey(
258+
uint code,
259+
uint mapType
260+
);
261+
262+
[DllImport("user32.dll")]
263+
public static extern bool TranslateMessage(
264+
ref MSG lpMsg
265+
);
266+
234267
[DllImport("api-ms-win-core-file-fromapp-l1-1-0.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
235268
public static extern IntPtr CreateFileFromApp(
236269
string lpFileName,

src/Files.App/Helpers/Win32/Win32PInvoke.Structs.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,5 +223,16 @@ public struct SHQUERYRBINFO
223223
public long i64Size;
224224
public long i64NumItems;
225225
}
226+
227+
[StructLayout(LayoutKind.Sequential)]
228+
public struct MSG
229+
{
230+
public IntPtr hwnd;
231+
public uint message;
232+
public IntPtr wParam;
233+
public IntPtr lParam;
234+
public uint time;
235+
public System.Drawing.Point pt;
236+
}
226237
}
227238
}

src/Files.App/Strings/en-US/Resources.resw

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3736,6 +3736,9 @@
37363736
<data name="KeybindingAlreadyUsedNotification" xml:space="preserve">
37373737
<value>This key binding is already being used, please choose a different key binding to continue.</value>
37383738
</data>
3739+
<data name="KeybindingInvalidKeyNotification" xml:space="preserve">
3740+
<value>The key binding you choose cannot be used, please try again using a different key binding.</value>
3741+
</data>
37393742
<data name="Customized" xml:space="preserve">
37403743
<value>Customized</value>
37413744
</data>
@@ -3823,6 +3826,9 @@
38233826
<value>Bitmap Files</value>
38243827
<comment>This is the friendly name for bitmap files.</comment>
38253828
</data>
3829+
<data name="NumPadTypeName" xml:space="preserve">
3830+
<value>Num</value>
3831+
</data>
38263832
<data name="NetworkLocations" xml:space="preserve">
38273833
<value>Network locations</value>
38283834
</data>

src/Files.App/UserControls/KeyboardShortcut/KeyboardShortcut.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@
136136
<Grid x:Name="PART_ContentRoot">
137137
<TextBlock
138138
x:Name="PART_MainTextTextBlock"
139-
Margin="2,0"
139+
Margin="2,2,2,2"
140140
VerticalAlignment="Top"
141141
Foreground="{ThemeResource TextFillColorTertiaryBrush}"
142142
LineHeight="16"

src/Files.App/ViewModels/Settings/ActionsViewModel.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ public bool IsAlreadyUsedTeachingTipOpened
3434
set => SetProperty(ref _IsAlreadyUsedTeachingTipOpened, value);
3535
}
3636

37+
private bool _IsInvalidKeyTeachingTipOpened;
38+
public bool IsInvalidKeyTeachingTipOpened
39+
{
40+
get => _IsInvalidKeyTeachingTipOpened;
41+
set => SetProperty(ref _IsInvalidKeyTeachingTipOpened, value);
42+
}
43+
3744
private bool _ShowAddNewKeyBindingBlock;
3845
public bool ShowAddNewKeyBindingBlock
3946
{
@@ -278,6 +285,9 @@ private void ExecuteEditCommand(ModifiableActionItem? item)
278285

279286
// Enter edit mode for the item
280287
item.IsInEditMode = true;
288+
289+
// Mark the key binding as invalid to prevent saving it
290+
item.IsValidKeyBinding = false;
281291
}
282292

283293
private void ExecuteSaveCommand(ModifiableActionItem? item)

src/Files.App/Views/Settings/ActionsPage.xaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@
319319
AutomationProperties.Name="{helpers:ResourceString Name=Save}"
320320
Command="{Binding ElementName=PageRoot, Path=DataContext.SaveCommand, Mode=OneWay}"
321321
CommandParameter="{x:Bind}"
322+
IsEnabled="{x:Bind IsValidKeyBinding, Mode=OneWay}"
322323
Style="{StaticResource AccentButtonStyle}"
323324
ToolTipService.ToolTip="{helpers:ResourceString Name=Save}">
324325
<FontIcon FontSize="14" Glyph="&#xE73E;" />
@@ -410,5 +411,13 @@
410411
PreferredPlacement="Auto"
411412
Subtitle="{helpers:ResourceString Name=KeybindingAlreadyUsedNotification}" />
412413

414+
<!-- Invalid Key TeachingTip -->
415+
<TeachingTip
416+
x:Name="InvalidKeyTeachingTip"
417+
IsLightDismissEnabled="True"
418+
IsOpen="{x:Bind ViewModel.IsInvalidKeyTeachingTipOpened, Mode=TwoWay}"
419+
PreferredPlacement="Bottom"
420+
Subtitle="{helpers:ResourceString Name=KeybindingInvalidKeyNotification}" />
421+
413422
</Grid>
414423
</Page>

0 commit comments

Comments
 (0)