From 81a46327223596a9cff55bd8ad4e1b332237d997 Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Sat, 22 Mar 2025 00:27:03 +0600 Subject: [PATCH 01/22] Implement auto-switching to English when the option is enabled --- Flow.Launcher/Helper/KeyboardLayoutHelper.cs | 95 ++++++++++++++++++++ Flow.Launcher/ViewModel/MainViewModel.cs | 13 ++- 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 Flow.Launcher/Helper/KeyboardLayoutHelper.cs diff --git a/Flow.Launcher/Helper/KeyboardLayoutHelper.cs b/Flow.Launcher/Helper/KeyboardLayoutHelper.cs new file mode 100644 index 00000000000..db62ef783c8 --- /dev/null +++ b/Flow.Launcher/Helper/KeyboardLayoutHelper.cs @@ -0,0 +1,95 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Flow.Launcher.Helper; + +public static class KeyboardLayoutHelper +{ + #region Windows API + + [DllImport("user32.dll")] + private static extern IntPtr GetKeyboardLayout(uint idThread); + + [DllImport("user32.dll")] + private static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr lpdwProcessId); + + [DllImport("user32.dll")] + private static extern IntPtr ActivateKeyboardLayout(IntPtr hkl, uint flags); + + [DllImport("user32.dll")] + private static extern int GetKeyboardLayoutList(int nBuff, [Out] IntPtr[] lpList); + + [DllImport("kernel32.dll")] + private static extern int GetLocaleInfoA(uint Locale, uint LCType, StringBuilder lpLCData, int cchData); + + [DllImport("user32.dll")] + private static extern IntPtr GetForegroundWindow(); + + // Used to get the language name of the keyboard layout + private const uint LOCALE_SLANGUAGE = 0x00000002; + + // The string to search for in the language name of a keyboard layout + private const string LAYOUT_ENGLISH_SEARCH = "english"; + + private static IntPtr FindEnglishKeyboardLayout() + { + // Get the number of keyboard layouts + var count = GetKeyboardLayoutList(0, null); + if (count <= 0) return IntPtr.Zero; + + // Get all keyboard layouts + var keyboardLayouts = new IntPtr[count]; + GetKeyboardLayoutList(count, keyboardLayouts); + + // Look for any English keyboard layout + foreach (var layout in keyboardLayouts) + { + // The lower word contains the language identifier + var langId = (uint)layout.ToInt32() & 0xFFFF; + + // Get language name for the layout + var sb = new StringBuilder(256); + GetLocaleInfoA(langId, LOCALE_SLANGUAGE, sb, sb.Capacity); + var langName = sb.ToString().ToLowerInvariant(); + + // Check if it's an English layout + if (langName.Contains(LAYOUT_ENGLISH_SEARCH)) + { + return layout; + } + } + + return IntPtr.Zero; + } + + #endregion + + // Query textbox keyboard layout + private static IntPtr _previousLayout; + + public static void SetEnglishKeyboardLayout() + { + // Find an installed English layout + var englishLayout = FindEnglishKeyboardLayout(); + + // No installed English layout found + if (englishLayout == IntPtr.Zero) return; + + var hwnd = GetForegroundWindow(); + var threadId = GetWindowThreadProcessId(hwnd, IntPtr.Zero); + + // Store current keyboard layout + _previousLayout = GetKeyboardLayout(threadId) & 0xFFFF; + + // Switch to English layout + ActivateKeyboardLayout(englishLayout, 0); + } + + public static void SetPreviousKeyboardLayout() + { + if (_previousLayout == IntPtr.Zero) return; + ActivateKeyboardLayout(_previousLayout, 0); + _previousLayout = IntPtr.Zero; + } +} diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 46970a6a13f..641825ec67d 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -14,6 +14,7 @@ using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.Input; using Flow.Launcher.Core.Plugin; +using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.Image; @@ -1373,6 +1374,11 @@ public void Show() MainWindowOpacity = 1; MainWindowVisibilityStatus = true; VisibilityChanged?.Invoke(this, new VisibilityChangedEventArgs { IsVisible = true }); + + if (StartWithEnglishMode) + { + KeyboardLayoutHelper.SetEnglishKeyboardLayout(); + } }); } @@ -1441,7 +1447,12 @@ public async void Hide() // 📌 Apply DWM Cloak (Completely hide the window) Win32Helper.DWMSetCloakForWindow(mainWindow, true); } - + + if (StartWithEnglishMode) + { + KeyboardLayoutHelper.SetPreviousKeyboardLayout(); + } + await Task.Delay(50); // Update WPF properties From aa3ad10044396092a7e6779542d389e2d76716a5 Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Sat, 22 Mar 2025 08:32:35 +0600 Subject: [PATCH 02/22] When looking for English keyboard layout, use pre-defined IDs instead of relying on layout name as a string --- Flow.Launcher/Helper/KeyboardLayoutHelper.cs | 23 +++++++------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/Flow.Launcher/Helper/KeyboardLayoutHelper.cs b/Flow.Launcher/Helper/KeyboardLayoutHelper.cs index db62ef783c8..e5458f6cdc9 100644 --- a/Flow.Launcher/Helper/KeyboardLayoutHelper.cs +++ b/Flow.Launcher/Helper/KeyboardLayoutHelper.cs @@ -1,6 +1,6 @@ using System; +using System.Linq; using System.Runtime.InteropServices; -using System.Text; namespace Flow.Launcher.Helper; @@ -20,17 +20,15 @@ public static class KeyboardLayoutHelper [DllImport("user32.dll")] private static extern int GetKeyboardLayoutList(int nBuff, [Out] IntPtr[] lpList); - [DllImport("kernel32.dll")] - private static extern int GetLocaleInfoA(uint Locale, uint LCType, StringBuilder lpLCData, int cchData); - [DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow(); - // Used to get the language name of the keyboard layout - private const uint LOCALE_SLANGUAGE = 0x00000002; - - // The string to search for in the language name of a keyboard layout - private const string LAYOUT_ENGLISH_SEARCH = "english"; + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f + private static readonly uint[] EnglishLanguageIds = + { + 0x0009, 0x0409, 0x0809, 0x0C09, 0x1000, 0x1009, 0x1409, 0x1809, 0x1C09, 0x2009, 0x2409, 0x2809, 0x2C09, + 0x3009, 0x3409, 0x3C09, 0x4009, 0x4409, 0x4809, 0x4C09, + }; private static IntPtr FindEnglishKeyboardLayout() { @@ -48,13 +46,8 @@ private static IntPtr FindEnglishKeyboardLayout() // The lower word contains the language identifier var langId = (uint)layout.ToInt32() & 0xFFFF; - // Get language name for the layout - var sb = new StringBuilder(256); - GetLocaleInfoA(langId, LOCALE_SLANGUAGE, sb, sb.Capacity); - var langName = sb.ToString().ToLowerInvariant(); - // Check if it's an English layout - if (langName.Contains(LAYOUT_ENGLISH_SEARCH)) + if (EnglishLanguageIds.Contains(langId)) { return layout; } From 023ab45022fab28b9969ea1df1521c8d2c3912e1 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 22 Mar 2025 12:06:44 +0800 Subject: [PATCH 03/22] Use PInvoke instead of DllImport & Several adjustments --- .../NativeMethods.txt | 7 +- Flow.Launcher.Infrastructure/Win32Helper.cs | 79 +++++++++++++++++ Flow.Launcher/Helper/KeyboardLayoutHelper.cs | 88 ------------------- Flow.Launcher/ViewModel/MainViewModel.cs | 5 +- 4 files changed, 87 insertions(+), 92 deletions(-) delete mode 100644 Flow.Launcher/Helper/KeyboardLayoutHelper.cs diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt index f080f24de15..d26478bbe9b 100644 --- a/Flow.Launcher.Infrastructure/NativeMethods.txt +++ b/Flow.Launcher.Infrastructure/NativeMethods.txt @@ -46,4 +46,9 @@ GetMonitorInfo MONITORINFOEXW WM_ENTERSIZEMOVE -WM_EXITSIZEMOVE \ No newline at end of file +WM_EXITSIZEMOVE + +GetKeyboardLayout +GetWindowThreadProcessId +ActivateKeyboardLayout +GetKeyboardLayoutList \ No newline at end of file diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 8dbe3f7e9eb..664f428ec6b 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using System.Linq; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Interop; @@ -7,8 +8,10 @@ using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.Graphics.Dwm; +using Windows.Win32.UI.Input.KeyboardAndMouse; using Windows.Win32.UI.WindowsAndMessaging; using Flow.Launcher.Infrastructure.UserSettings; +using Point = System.Windows.Point; namespace Flow.Launcher.Infrastructure { @@ -317,5 +320,81 @@ internal static HWND GetWindowHandle(Window window, bool ensure = false) } #endregion + + #region Keyboard Layout + + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f + private static readonly uint[] EnglishLanguageIds = + { + 0x0009, 0x0409, 0x0809, 0x0C09, 0x1000, 0x1009, 0x1409, 0x1809, 0x1C09, 0x2009, 0x2409, 0x2809, 0x2C09, + 0x3009, 0x3409, 0x3C09, 0x4009, 0x4409, 0x4809, 0x4C09, + }; + + // Store the previous keyboard layout + private static HKL _previousLayout; + + private static unsafe HKL FindEnglishKeyboardLayout() + { + // Get the number of keyboard layouts + int count = PInvoke.GetKeyboardLayoutList(0, null); + if (count <= 0) return HKL.Null; + + // Get all keyboard layouts + var handles = new HKL[count]; + fixed (HKL* h = handles) + { + _ = PInvoke.GetKeyboardLayoutList(count, h); + } + + // Look for any English keyboard layout + foreach (var hkl in handles) + { + // The lower word contains the language identifier + var langId = (uint)hkl.Value & 0xFFFF; + + // Check if it's an English layout + if (EnglishLanguageIds.Contains(langId)) + { + return hkl; + } + } + + return HKL.Null; + } + + public static unsafe void SetEnglishKeyboardLayout(bool backupPrevious) + { + // Find an installed English layout + var enHKL = FindEnglishKeyboardLayout(); + + // No installed English layout found + if (enHKL == HKL.Null) return; + + // Get the current window thread ID + uint threadId = 0; + var result = PInvoke.GetWindowThreadProcessId(PInvoke.GetForegroundWindow(), &threadId); + if (result == 0 || threadId == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); + + // Backup current keyboard layout + if (backupPrevious) + { + _previousLayout = PInvoke.GetKeyboardLayout(threadId); + } + + // Switch to English layout + PInvoke.ActivateKeyboardLayout(enHKL, 0); + } + + public static void SetPreviousKeyboardLayout() + { + if (_previousLayout != HKL.Null) + { + PInvoke.ActivateKeyboardLayout(_previousLayout, 0); + + _previousLayout = HKL.Null; + } + } + + #endregion } } diff --git a/Flow.Launcher/Helper/KeyboardLayoutHelper.cs b/Flow.Launcher/Helper/KeyboardLayoutHelper.cs deleted file mode 100644 index e5458f6cdc9..00000000000 --- a/Flow.Launcher/Helper/KeyboardLayoutHelper.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Linq; -using System.Runtime.InteropServices; - -namespace Flow.Launcher.Helper; - -public static class KeyboardLayoutHelper -{ - #region Windows API - - [DllImport("user32.dll")] - private static extern IntPtr GetKeyboardLayout(uint idThread); - - [DllImport("user32.dll")] - private static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr lpdwProcessId); - - [DllImport("user32.dll")] - private static extern IntPtr ActivateKeyboardLayout(IntPtr hkl, uint flags); - - [DllImport("user32.dll")] - private static extern int GetKeyboardLayoutList(int nBuff, [Out] IntPtr[] lpList); - - [DllImport("user32.dll")] - private static extern IntPtr GetForegroundWindow(); - - // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f - private static readonly uint[] EnglishLanguageIds = - { - 0x0009, 0x0409, 0x0809, 0x0C09, 0x1000, 0x1009, 0x1409, 0x1809, 0x1C09, 0x2009, 0x2409, 0x2809, 0x2C09, - 0x3009, 0x3409, 0x3C09, 0x4009, 0x4409, 0x4809, 0x4C09, - }; - - private static IntPtr FindEnglishKeyboardLayout() - { - // Get the number of keyboard layouts - var count = GetKeyboardLayoutList(0, null); - if (count <= 0) return IntPtr.Zero; - - // Get all keyboard layouts - var keyboardLayouts = new IntPtr[count]; - GetKeyboardLayoutList(count, keyboardLayouts); - - // Look for any English keyboard layout - foreach (var layout in keyboardLayouts) - { - // The lower word contains the language identifier - var langId = (uint)layout.ToInt32() & 0xFFFF; - - // Check if it's an English layout - if (EnglishLanguageIds.Contains(langId)) - { - return layout; - } - } - - return IntPtr.Zero; - } - - #endregion - - // Query textbox keyboard layout - private static IntPtr _previousLayout; - - public static void SetEnglishKeyboardLayout() - { - // Find an installed English layout - var englishLayout = FindEnglishKeyboardLayout(); - - // No installed English layout found - if (englishLayout == IntPtr.Zero) return; - - var hwnd = GetForegroundWindow(); - var threadId = GetWindowThreadProcessId(hwnd, IntPtr.Zero); - - // Store current keyboard layout - _previousLayout = GetKeyboardLayout(threadId) & 0xFFFF; - - // Switch to English layout - ActivateKeyboardLayout(englishLayout, 0); - } - - public static void SetPreviousKeyboardLayout() - { - if (_previousLayout == IntPtr.Zero) return; - ActivateKeyboardLayout(_previousLayout, 0); - _previousLayout = IntPtr.Zero; - } -} diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 641825ec67d..0f355a80f84 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -14,7 +14,6 @@ using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.Input; using Flow.Launcher.Core.Plugin; -using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.Image; @@ -1377,7 +1376,7 @@ public void Show() if (StartWithEnglishMode) { - KeyboardLayoutHelper.SetEnglishKeyboardLayout(); + Win32Helper.SetEnglishKeyboardLayout(true); } }); } @@ -1450,7 +1449,7 @@ public async void Hide() if (StartWithEnglishMode) { - KeyboardLayoutHelper.SetPreviousKeyboardLayout(); + Win32Helper.SetPreviousKeyboardLayout(); } await Task.Delay(50); From 465108a9d487a0aee8d7bdb253a5e983bbfa715a Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 22 Mar 2025 13:58:53 +0800 Subject: [PATCH 04/22] Fix keyboard layout fetch issue --- Flow.Launcher.Infrastructure/Win32Helper.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 664f428ec6b..2248fadd673 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -371,9 +371,8 @@ public static unsafe void SetEnglishKeyboardLayout(bool backupPrevious) if (enHKL == HKL.Null) return; // Get the current window thread ID - uint threadId = 0; - var result = PInvoke.GetWindowThreadProcessId(PInvoke.GetForegroundWindow(), &threadId); - if (result == 0 || threadId == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); + var threadId = PInvoke.GetWindowThreadProcessId(PInvoke.GetForegroundWindow()); + if (threadId == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); // Backup current keyboard layout if (backupPrevious) From 67be335600d96d1a8077b340c5848819208b0a9e Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Sat, 22 Mar 2025 14:58:42 +0600 Subject: [PATCH 05/22] Rename methods to make their purpose more obvious; slight code style changes --- Flow.Launcher.Infrastructure/Win32Helper.cs | 14 ++++++-------- Flow.Launcher/ViewModel/MainViewModel.cs | 12 ++++++------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 2248fadd673..3d8821fcaee 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -66,7 +66,7 @@ public static unsafe bool DWMSetDarkModeForWindow(Window window, bool useDarkMod } /// - /// + /// /// /// /// DoNotRound, Round, RoundSmall, Default @@ -362,7 +362,7 @@ private static unsafe HKL FindEnglishKeyboardLayout() return HKL.Null; } - public static unsafe void SetEnglishKeyboardLayout(bool backupPrevious) + public static unsafe void SwitchToEnglishKeyboardLayout(bool backupPrevious) { // Find an installed English layout var enHKL = FindEnglishKeyboardLayout(); @@ -384,14 +384,12 @@ public static unsafe void SetEnglishKeyboardLayout(bool backupPrevious) PInvoke.ActivateKeyboardLayout(enHKL, 0); } - public static void SetPreviousKeyboardLayout() + public static void RestorePreviousKeyboardLayout() { - if (_previousLayout != HKL.Null) - { - PInvoke.ActivateKeyboardLayout(_previousLayout, 0); + if (_previousLayout == HKL.Null) return; - _previousLayout = HKL.Null; - } + PInvoke.ActivateKeyboardLayout(_previousLayout, 0); + _previousLayout = HKL.Null; } #endregion diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 0f355a80f84..5ebed036961 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1376,7 +1376,7 @@ public void Show() if (StartWithEnglishMode) { - Win32Helper.SetEnglishKeyboardLayout(true); + Win32Helper.SwitchToEnglishKeyboardLayout(true); } }); } @@ -1385,6 +1385,11 @@ public void Show() public async void Hide() { + + if (StartWithEnglishMode) + { + Win32Helper.RestorePreviousKeyboardLayout(); + } lastHistoryIndex = 1; if (ExternalPreviewVisible) @@ -1447,11 +1452,6 @@ public async void Hide() Win32Helper.DWMSetCloakForWindow(mainWindow, true); } - if (StartWithEnglishMode) - { - Win32Helper.SetPreviousKeyboardLayout(); - } - await Task.Delay(50); // Update WPF properties From c39079badc789a05bea0d23fbd37ab1c22641ecb Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Sat, 22 Mar 2025 15:35:34 +0600 Subject: [PATCH 06/22] Revert accidental change --- Flow.Launcher/ViewModel/MainViewModel.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 5ebed036961..59af95da539 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1385,11 +1385,6 @@ public void Show() public async void Hide() { - - if (StartWithEnglishMode) - { - Win32Helper.RestorePreviousKeyboardLayout(); - } lastHistoryIndex = 1; if (ExternalPreviewVisible) @@ -1452,6 +1447,11 @@ public async void Hide() Win32Helper.DWMSetCloakForWindow(mainWindow, true); } + if (StartWithEnglishMode) + { + Win32Helper.RestorePreviousKeyboardLayout(); + } + await Task.Delay(50); // Update WPF properties From 4146f4d3096cd44b5e81435d50d8df1711cd3a3d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 22 Mar 2025 18:03:41 +0800 Subject: [PATCH 07/22] Use focus events to trigger --- Flow.Launcher/MainWindow.xaml | 2 ++ Flow.Launcher/MainWindow.xaml.cs | 16 ++++++++++++++++ Flow.Launcher/ViewModel/MainViewModel.cs | 10 ---------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 5b63303acf7..2440e89fa40 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -242,6 +242,8 @@ InputMethod.PreferredImeState="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEStateConverter}}" PreviewDragOver="QueryTextBox_OnPreviewDragOver" PreviewKeyUp="QueryTextBox_KeyUp" + GotKeyboardFocus="QueryTextBox_OnGotKeyboardFocus" + PreviewLostKeyboardFocus="QueryTextBox_OnPreviewLostKeyboardFocus" Style="{DynamicResource QueryBoxStyle}" Text="{Binding QueryText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Visibility="Visible" diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 2ce3d1e95e6..bf560e621e4 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -989,6 +989,22 @@ private void QueryTextBox_OnPreviewDragOver(object sender, DragEventArgs e) e.Handled = true; } + private void QueryTextBox_OnGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) + { + if (_viewModel.StartWithEnglishMode) + { + Win32Helper.SwitchToEnglishKeyboardLayout(true); + } + } + + private void QueryTextBox_OnPreviewLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) + { + if (_viewModel.StartWithEnglishMode) + { + Win32Helper.RestorePreviousKeyboardLayout(); + } + } + #endregion } } diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 59af95da539..97ed422c55a 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1373,11 +1373,6 @@ public void Show() MainWindowOpacity = 1; MainWindowVisibilityStatus = true; VisibilityChanged?.Invoke(this, new VisibilityChangedEventArgs { IsVisible = true }); - - if (StartWithEnglishMode) - { - Win32Helper.SwitchToEnglishKeyboardLayout(true); - } }); } @@ -1447,11 +1442,6 @@ public async void Hide() Win32Helper.DWMSetCloakForWindow(mainWindow, true); } - if (StartWithEnglishMode) - { - Win32Helper.RestorePreviousKeyboardLayout(); - } - await Task.Delay(50); // Update WPF properties From f83e8eddb6aad1ed6cbec06620db6f1b1e6dab74 Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Sat, 22 Mar 2025 16:23:34 +0600 Subject: [PATCH 08/22] Revert "Use focus events to trigger" This reverts commit 4146f4d3096cd44b5e81435d50d8df1711cd3a3d. --- Flow.Launcher/MainWindow.xaml | 2 -- Flow.Launcher/MainWindow.xaml.cs | 16 ---------------- Flow.Launcher/ViewModel/MainViewModel.cs | 10 ++++++++++ 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 2440e89fa40..5b63303acf7 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -242,8 +242,6 @@ InputMethod.PreferredImeState="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEStateConverter}}" PreviewDragOver="QueryTextBox_OnPreviewDragOver" PreviewKeyUp="QueryTextBox_KeyUp" - GotKeyboardFocus="QueryTextBox_OnGotKeyboardFocus" - PreviewLostKeyboardFocus="QueryTextBox_OnPreviewLostKeyboardFocus" Style="{DynamicResource QueryBoxStyle}" Text="{Binding QueryText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Visibility="Visible" diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index bf560e621e4..2ce3d1e95e6 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -989,22 +989,6 @@ private void QueryTextBox_OnPreviewDragOver(object sender, DragEventArgs e) e.Handled = true; } - private void QueryTextBox_OnGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) - { - if (_viewModel.StartWithEnglishMode) - { - Win32Helper.SwitchToEnglishKeyboardLayout(true); - } - } - - private void QueryTextBox_OnPreviewLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) - { - if (_viewModel.StartWithEnglishMode) - { - Win32Helper.RestorePreviousKeyboardLayout(); - } - } - #endregion } } diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 97ed422c55a..59af95da539 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1373,6 +1373,11 @@ public void Show() MainWindowOpacity = 1; MainWindowVisibilityStatus = true; VisibilityChanged?.Invoke(this, new VisibilityChangedEventArgs { IsVisible = true }); + + if (StartWithEnglishMode) + { + Win32Helper.SwitchToEnglishKeyboardLayout(true); + } }); } @@ -1442,6 +1447,11 @@ public async void Hide() Win32Helper.DWMSetCloakForWindow(mainWindow, true); } + if (StartWithEnglishMode) + { + Win32Helper.RestorePreviousKeyboardLayout(); + } + await Task.Delay(50); // Update WPF properties From 6ad4b2355ed23a4df7693f3b12221908893acfad Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Sat, 22 Mar 2025 17:08:09 +0600 Subject: [PATCH 09/22] Don't switch to English when IME can be disabled instead --- Flow.Launcher.Infrastructure/Win32Helper.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 3d8821fcaee..76d469414a7 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -330,6 +330,13 @@ internal static HWND GetWindowHandle(Window window, bool ensure = false) 0x3009, 0x3409, 0x3C09, 0x4009, 0x4409, 0x4809, 0x4C09, }; + private static readonly uint[] ImeLanguageIds = + { + 0x0004, 0x7804, 0x0804, 0x1004, 0x7C04, 0x0C04, 0x1404, 0x0404, 0x0011, 0x0411, 0x0012, 0x0412, + }; + + private const uint KeyboardLayoutLoWord = 0xFFFF; + // Store the previous keyboard layout private static HKL _previousLayout; @@ -350,7 +357,7 @@ private static unsafe HKL FindEnglishKeyboardLayout() foreach (var hkl in handles) { // The lower word contains the language identifier - var langId = (uint)hkl.Value & 0xFFFF; + var langId = (uint)hkl.Value & KeyboardLayoutLoWord; // Check if it's an English layout if (EnglishLanguageIds.Contains(langId)) @@ -374,10 +381,18 @@ public static unsafe void SwitchToEnglishKeyboardLayout(bool backupPrevious) var threadId = PInvoke.GetWindowThreadProcessId(PInvoke.GetForegroundWindow()); if (threadId == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); + // If the current layout has an IME mode, disable it without switching to another layout + var currentLayout = PInvoke.GetKeyboardLayout(threadId); + var currentLayoutCode = (uint)currentLayout.Value & KeyboardLayoutLoWord; + if (ImeLanguageIds.Contains(currentLayoutCode)) + { + return; + } + // Backup current keyboard layout if (backupPrevious) { - _previousLayout = PInvoke.GetKeyboardLayout(threadId); + _previousLayout = currentLayout; } // Switch to English layout From ca04823dd77be4ba24d5a7dee69275049dd78eb8 Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Sat, 22 Mar 2025 19:40:26 +0600 Subject: [PATCH 10/22] Remove generic language code --- Flow.Launcher.Infrastructure/Win32Helper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 76d469414a7..f23f0244730 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -326,8 +326,8 @@ internal static HWND GetWindowHandle(Window window, bool ensure = false) // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f private static readonly uint[] EnglishLanguageIds = { - 0x0009, 0x0409, 0x0809, 0x0C09, 0x1000, 0x1009, 0x1409, 0x1809, 0x1C09, 0x2009, 0x2409, 0x2809, 0x2C09, - 0x3009, 0x3409, 0x3C09, 0x4009, 0x4409, 0x4809, 0x4C09, + 0x0009, 0x0409, 0x0809, 0x0C09, 0x1009, 0x1409, 0x1809, 0x1C09, 0x2009, 0x2409, 0x2809, 0x2C09, 0x3009, + 0x3409, 0x3C09, 0x4009, 0x4409, 0x4809, 0x4C09, }; private static readonly uint[] ImeLanguageIds = From 747f9582c3673a72eaf707e765f3badfc5af6674 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 23 Mar 2025 19:27:40 +0800 Subject: [PATCH 11/22] Fix keyboard restore issue when window is deactivated --- Flow.Launcher/MainWindow.xaml.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 2ce3d1e95e6..de15f30e8d3 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -250,11 +250,21 @@ private void OnLocationChanged(object sender, EventArgs e) private async void OnDeactivated(object sender, EventArgs e) { + // When window is deactivated, FL cannot set keyboard correctly + // This is a workaround to restore the keyboard layout + if (_settings.HideWhenDeactivated && _viewModel.StartWithEnglishMode) + { + Activate(); + QueryTextBox.Focus(); + Win32Helper.RestorePreviousKeyboardLayout(); + } + _settings.WindowLeft = Left; _settings.WindowTop = Top; ClockPanel.Opacity = 0; SearchIcon.Opacity = 0; - //This condition stops extra hide call when animator is on, + + // This condition stops extra hide call when animator is on, // which causes the toggling to occasional hide instead of show. if (_viewModel.MainWindowVisibilityStatus) { @@ -262,8 +272,9 @@ private async void OnDeactivated(object sender, EventArgs e) // This also stops the mainwindow from flickering occasionally after Settings window is opened // and always after Settings window is closed. if (_settings.UseAnimation) - + { await Task.Delay(100); + } if (_settings.HideWhenDeactivated && !_viewModel.ExternalPreviewVisible) { From cd28c09c09b3a30a78571a9448db07fba2303e72 Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Sun, 23 Mar 2025 17:56:52 +0600 Subject: [PATCH 12/22] Fix the issue with not being able to switch back to the original keyboard layout in OnDeactivated --- Flow.Launcher.Infrastructure/NativeMethods.txt | 7 ++++++- Flow.Launcher.Infrastructure/Win32Helper.cs | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt index d26478bbe9b..54ae099f8c8 100644 --- a/Flow.Launcher.Infrastructure/NativeMethods.txt +++ b/Flow.Launcher.Infrastructure/NativeMethods.txt @@ -51,4 +51,9 @@ WM_EXITSIZEMOVE GetKeyboardLayout GetWindowThreadProcessId ActivateKeyboardLayout -GetKeyboardLayoutList \ No newline at end of file +GetKeyboardLayoutList + +PostMessage +HWND_BROADCAST +WM_INPUTLANGCHANGEREQUEST +INPUTLANGCHANGE_FORWARD diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index f23f0244730..197a5a55671 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -403,7 +403,11 @@ public static void RestorePreviousKeyboardLayout() { if (_previousLayout == HKL.Null) return; - PInvoke.ActivateKeyboardLayout(_previousLayout, 0); + PInvoke.PostMessage(HWND.HWND_BROADCAST, + PInvoke.WM_INPUTLANGCHANGEREQUEST, + PInvoke.INPUTLANGCHANGE_FORWARD, + _previousLayout.Value + ); _previousLayout = HKL.Null; } From bf011f11dbac1b28e7cbfc15e1e60157a1c0bc84 Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Sun, 23 Mar 2025 17:57:24 +0600 Subject: [PATCH 13/22] Revert "Fix keyboard restore issue when window is deactivated" This reverts commit 747f9582c3673a72eaf707e765f3badfc5af6674. --- Flow.Launcher/MainWindow.xaml.cs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index de15f30e8d3..2ce3d1e95e6 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -250,21 +250,11 @@ private void OnLocationChanged(object sender, EventArgs e) private async void OnDeactivated(object sender, EventArgs e) { - // When window is deactivated, FL cannot set keyboard correctly - // This is a workaround to restore the keyboard layout - if (_settings.HideWhenDeactivated && _viewModel.StartWithEnglishMode) - { - Activate(); - QueryTextBox.Focus(); - Win32Helper.RestorePreviousKeyboardLayout(); - } - _settings.WindowLeft = Left; _settings.WindowTop = Top; ClockPanel.Opacity = 0; SearchIcon.Opacity = 0; - - // This condition stops extra hide call when animator is on, + //This condition stops extra hide call when animator is on, // which causes the toggling to occasional hide instead of show. if (_viewModel.MainWindowVisibilityStatus) { @@ -272,9 +262,8 @@ private async void OnDeactivated(object sender, EventArgs e) // This also stops the mainwindow from flickering occasionally after Settings window is opened // and always after Settings window is closed. if (_settings.UseAnimation) - { + await Task.Delay(100); - } if (_settings.HideWhenDeactivated && !_viewModel.ExternalPreviewVisible) { From 382d0c2bfe85416185d9ffcebfc0d9ab68065f31 Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Sun, 23 Mar 2025 18:26:11 +0600 Subject: [PATCH 14/22] Don't broadcast language change --- Flow.Launcher.Infrastructure/NativeMethods.txt | 1 - Flow.Launcher.Infrastructure/Win32Helper.cs | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt index 54ae099f8c8..833c6682b62 100644 --- a/Flow.Launcher.Infrastructure/NativeMethods.txt +++ b/Flow.Launcher.Infrastructure/NativeMethods.txt @@ -54,6 +54,5 @@ ActivateKeyboardLayout GetKeyboardLayoutList PostMessage -HWND_BROADCAST WM_INPUTLANGCHANGEREQUEST INPUTLANGCHANGE_FORWARD diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 197a5a55671..7bb9df2e766 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -403,7 +403,9 @@ public static void RestorePreviousKeyboardLayout() { if (_previousLayout == HKL.Null) return; - PInvoke.PostMessage(HWND.HWND_BROADCAST, + var hwnd = PInvoke.GetForegroundWindow(); + PInvoke.PostMessage( + hwnd, PInvoke.WM_INPUTLANGCHANGEREQUEST, PInvoke.INPUTLANGCHANGE_FORWARD, _previousLayout.Value From 48aff32f1b9a699204766c1284bc1373a37fb218 Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Sun, 23 Mar 2025 18:28:19 +0600 Subject: [PATCH 15/22] Clarify why not switch keyboard layout for languages that have IME mode --- Flow.Launcher.Infrastructure/Win32Helper.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 7bb9df2e766..f874c700641 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -381,7 +381,9 @@ public static unsafe void SwitchToEnglishKeyboardLayout(bool backupPrevious) var threadId = PInvoke.GetWindowThreadProcessId(PInvoke.GetForegroundWindow()); if (threadId == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); - // If the current layout has an IME mode, disable it without switching to another layout + // If the current layout has an IME mode, disable it without switching to another layout. + // This is needed because for languages with IME mode, Flow Launcher just temporarily disables + // the IME mode instead of switching to another layout. var currentLayout = PInvoke.GetKeyboardLayout(threadId); var currentLayoutCode = (uint)currentLayout.Value & KeyboardLayoutLoWord; if (ImeLanguageIds.Contains(currentLayoutCode)) From 4df42a0f638ea0dc2a4be6999606cc1ea1141e63 Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Sun, 23 Mar 2025 18:40:41 +0600 Subject: [PATCH 16/22] Add doc comments and additional error handling in keyboard layout switch logic --- Flow.Launcher.Infrastructure/Win32Helper.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index f874c700641..7cebedfdc1a 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -350,7 +350,11 @@ private static unsafe HKL FindEnglishKeyboardLayout() var handles = new HKL[count]; fixed (HKL* h = handles) { - _ = PInvoke.GetKeyboardLayoutList(count, h); + var result = PInvoke.GetKeyboardLayoutList(count, h); + if (result != 0) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } } // Look for any English keyboard layout @@ -369,6 +373,11 @@ private static unsafe HKL FindEnglishKeyboardLayout() return HKL.Null; } + /// + /// Switches the keyboard layout to English if available. + /// + /// If true, the current keyboard layout will be stored for later restoration. + /// Thrown when there's an error getting the window thread process ID. public static unsafe void SwitchToEnglishKeyboardLayout(bool backupPrevious) { // Find an installed English layout @@ -401,11 +410,17 @@ public static unsafe void SwitchToEnglishKeyboardLayout(bool backupPrevious) PInvoke.ActivateKeyboardLayout(enHKL, 0); } + /// + /// Restores the previously backed-up keyboard layout. + /// If it wasn't backed up or has already been restored, this method does nothing. + /// public static void RestorePreviousKeyboardLayout() { if (_previousLayout == HKL.Null) return; var hwnd = PInvoke.GetForegroundWindow(); + if (hwnd == HWND.Null) return; + PInvoke.PostMessage( hwnd, PInvoke.WM_INPUTLANGCHANGEREQUEST, From 1bf573344ae676adb2d22198e0d10e7b9a3cb5ff Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Sun, 23 Mar 2025 18:53:01 +0600 Subject: [PATCH 17/22] Fix incorrect error handling logic in keyboard layout change --- Flow.Launcher.Infrastructure/Win32Helper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 7cebedfdc1a..108f51fd410 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -351,7 +351,7 @@ private static unsafe HKL FindEnglishKeyboardLayout() fixed (HKL* h = handles) { var result = PInvoke.GetKeyboardLayoutList(count, h); - if (result != 0) + if (result == 0) { throw new Win32Exception(Marshal.GetLastWin32Error()); } From 5be88dd38613bc1f110b8d0437611ff396502c9c Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 23 Mar 2025 20:57:12 +0800 Subject: [PATCH 18/22] Remove blank line --- Flow.Launcher.Infrastructure/NativeMethods.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt index 833c6682b62..de74eb7f961 100644 --- a/Flow.Launcher.Infrastructure/NativeMethods.txt +++ b/Flow.Launcher.Infrastructure/NativeMethods.txt @@ -52,7 +52,6 @@ GetKeyboardLayout GetWindowThreadProcessId ActivateKeyboardLayout GetKeyboardLayoutList - PostMessage WM_INPUTLANGCHANGEREQUEST INPUTLANGCHANGE_FORWARD From c63debe296ec40bda08a8083bd667aec7a549f32 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 23 Mar 2025 20:57:25 +0800 Subject: [PATCH 19/22] Add foreground window check --- Flow.Launcher.Infrastructure/Win32Helper.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 108f51fd410..226d7b76cdb 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -386,8 +386,12 @@ public static unsafe void SwitchToEnglishKeyboardLayout(bool backupPrevious) // No installed English layout found if (enHKL == HKL.Null) return; - // Get the current window thread ID - var threadId = PInvoke.GetWindowThreadProcessId(PInvoke.GetForegroundWindow()); + // Get the current foreground window + var hwnd = PInvoke.GetForegroundWindow(); + if (hwnd == HWND.Null) return; + + // Get the current foreground window thread ID + var threadId = PInvoke.GetWindowThreadProcessId(hwnd); if (threadId == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); // If the current layout has an IME mode, disable it without switching to another layout. From 4fc7f70d18b4b284f1814f44addceda457f87f52 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 23 Mar 2025 21:05:14 +0800 Subject: [PATCH 20/22] Adjust formats --- Flow.Launcher.Infrastructure/Win32Helper.cs | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 226d7b76cdb..8c32899ecd8 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -351,10 +351,7 @@ private static unsafe HKL FindEnglishKeyboardLayout() fixed (HKL* h = handles) { var result = PInvoke.GetKeyboardLayoutList(count, h); - if (result == 0) - { - throw new Win32Exception(Marshal.GetLastWin32Error()); - } + if (result == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); } // Look for any English keyboard layout @@ -364,10 +361,7 @@ private static unsafe HKL FindEnglishKeyboardLayout() var langId = (uint)hkl.Value & KeyboardLayoutLoWord; // Check if it's an English layout - if (EnglishLanguageIds.Contains(langId)) - { - return hkl; - } + if (EnglishLanguageIds.Contains(langId)) return hkl; } return HKL.Null; @@ -399,16 +393,10 @@ public static unsafe void SwitchToEnglishKeyboardLayout(bool backupPrevious) // the IME mode instead of switching to another layout. var currentLayout = PInvoke.GetKeyboardLayout(threadId); var currentLayoutCode = (uint)currentLayout.Value & KeyboardLayoutLoWord; - if (ImeLanguageIds.Contains(currentLayoutCode)) - { - return; - } + if (ImeLanguageIds.Contains(currentLayoutCode)) return; // Backup current keyboard layout - if (backupPrevious) - { - _previousLayout = currentLayout; - } + if (backupPrevious) _previousLayout = currentLayout; // Switch to English layout PInvoke.ActivateKeyboardLayout(enHKL, 0); @@ -431,6 +419,7 @@ public static void RestorePreviousKeyboardLayout() PInvoke.INPUTLANGCHANGE_FORWARD, _previousLayout.Value ); + _previousLayout = HKL.Null; } From d827d0ac9f42f88556476731c700c6ad8806e8fc Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 23 Mar 2025 22:22:08 +0800 Subject: [PATCH 21/22] Use language tag instead of language id --- .../NativeMethods.txt | 4 + Flow.Launcher.Infrastructure/Win32Helper.cs | 141 +++++++++++++----- 2 files changed, 107 insertions(+), 38 deletions(-) diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt index de74eb7f961..363ecb9d002 100644 --- a/Flow.Launcher.Infrastructure/NativeMethods.txt +++ b/Flow.Launcher.Infrastructure/NativeMethods.txt @@ -55,3 +55,7 @@ GetKeyboardLayoutList PostMessage WM_INPUTLANGCHANGEREQUEST INPUTLANGCHANGE_FORWARD +LOCALE_TRANSIENT_KEYBOARD1 +LOCALE_TRANSIENT_KEYBOARD2 +LOCALE_TRANSIENT_KEYBOARD3 +LOCALE_TRANSIENT_KEYBOARD4 \ No newline at end of file diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 8c32899ecd8..c84c2e3a8a8 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -1,16 +1,17 @@ using System; using System.ComponentModel; -using System.Linq; +using System.Globalization; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Interop; using System.Windows.Media; +using Flow.Launcher.Infrastructure.UserSettings; +using Microsoft.Win32; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.Graphics.Dwm; using Windows.Win32.UI.Input.KeyboardAndMouse; using Windows.Win32.UI.WindowsAndMessaging; -using Flow.Launcher.Infrastructure.UserSettings; using Point = System.Windows.Point; namespace Flow.Launcher.Infrastructure @@ -323,16 +324,16 @@ internal static HWND GetWindowHandle(Window window, bool ensure = false) #region Keyboard Layout + private const string UserProfileRegistryPath = @"Control Panel\International\User Profile"; + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f - private static readonly uint[] EnglishLanguageIds = - { - 0x0009, 0x0409, 0x0809, 0x0C09, 0x1009, 0x1409, 0x1809, 0x1C09, 0x2009, 0x2409, 0x2809, 0x2C09, 0x3009, - 0x3409, 0x3C09, 0x4009, 0x4409, 0x4809, 0x4C09, - }; + private const string EnglishLanguageTag = "en"; - private static readonly uint[] ImeLanguageIds = + private static readonly string[] ImeLanguageTags = { - 0x0004, 0x7804, 0x0804, 0x1004, 0x7C04, 0x0C04, 0x1404, 0x0404, 0x0011, 0x0411, 0x0012, 0x0412, + "zh", // Chinese + "ja", // Japanese + "ko", // Korean }; private const uint KeyboardLayoutLoWord = 0xFFFF; @@ -340,33 +341,6 @@ internal static HWND GetWindowHandle(Window window, bool ensure = false) // Store the previous keyboard layout private static HKL _previousLayout; - private static unsafe HKL FindEnglishKeyboardLayout() - { - // Get the number of keyboard layouts - int count = PInvoke.GetKeyboardLayoutList(0, null); - if (count <= 0) return HKL.Null; - - // Get all keyboard layouts - var handles = new HKL[count]; - fixed (HKL* h = handles) - { - var result = PInvoke.GetKeyboardLayoutList(count, h); - if (result == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); - } - - // Look for any English keyboard layout - foreach (var hkl in handles) - { - // The lower word contains the language identifier - var langId = (uint)hkl.Value & KeyboardLayoutLoWord; - - // Check if it's an English layout - if (EnglishLanguageIds.Contains(langId)) return hkl; - } - - return HKL.Null; - } - /// /// Switches the keyboard layout to English if available. /// @@ -392,8 +366,14 @@ public static unsafe void SwitchToEnglishKeyboardLayout(bool backupPrevious) // This is needed because for languages with IME mode, Flow Launcher just temporarily disables // the IME mode instead of switching to another layout. var currentLayout = PInvoke.GetKeyboardLayout(threadId); - var currentLayoutCode = (uint)currentLayout.Value & KeyboardLayoutLoWord; - if (ImeLanguageIds.Contains(currentLayoutCode)) return; + var currentLangId = (uint)currentLayout.Value & KeyboardLayoutLoWord; + foreach (var langTag in ImeLanguageTags) + { + if (GetLanguageTag(currentLangId).StartsWith(langTag, StringComparison.OrdinalIgnoreCase)) + { + return; + } + } // Backup current keyboard layout if (backupPrevious) _previousLayout = currentLayout; @@ -423,6 +403,91 @@ public static void RestorePreviousKeyboardLayout() _previousLayout = HKL.Null; } + /// + /// Finds an installed English keyboard layout. + /// + /// + /// + private static unsafe HKL FindEnglishKeyboardLayout() + { + // Get the number of keyboard layouts + int count = PInvoke.GetKeyboardLayoutList(0, null); + if (count <= 0) return HKL.Null; + + // Get all keyboard layouts + var handles = new HKL[count]; + fixed (HKL* h = handles) + { + var result = PInvoke.GetKeyboardLayoutList(count, h); + if (result == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + // Look for any English keyboard layout + foreach (var hkl in handles) + { + // The lower word contains the language identifier + var langId = (uint)hkl.Value & KeyboardLayoutLoWord; + var langTag = GetLanguageTag(langId); + + // Check if it's an English layout + if (langTag.StartsWith(EnglishLanguageTag, StringComparison.OrdinalIgnoreCase)) + { + return hkl; + } + } + + return HKL.Null; + } + + /// + /// Returns the + /// + /// BCP 47 language tag + /// + /// of the current input language. + /// + /// + /// Edited from: https://github.com/dotnet/winforms + /// + private static string GetLanguageTag(uint langId) + { + // We need to convert the language identifier to a language tag, because they are deprecated and may have a + // transient value. + // https://learn.microsoft.com/globalization/locale/other-locale-names#lcid + // https://learn.microsoft.com/windows/win32/winmsg/wm-inputlangchange#remarks + // + // It turns out that the LCIDToLocaleName API, which is used inside CultureInfo, may return incorrect + // language tags for transient language identifiers. For example, it returns "nqo-GN" and "jv-Java-ID" + // instead of the "nqo" and "jv-Java" (as seen in the Get-WinUserLanguageList PowerShell cmdlet). + // + // Try to extract proper language tag from registry as a workaround approved by a Windows team. + // https://github.com/dotnet/winforms/pull/8573#issuecomment-1542600949 + // + // NOTE: this logic may break in future versions of Windows since it is not documented. + if (langId is (int)PInvoke.LOCALE_TRANSIENT_KEYBOARD1 + or (int)PInvoke.LOCALE_TRANSIENT_KEYBOARD2 + or (int)PInvoke.LOCALE_TRANSIENT_KEYBOARD3 + or (int)PInvoke.LOCALE_TRANSIENT_KEYBOARD4) + { + using RegistryKey key = Registry.CurrentUser.OpenSubKey(UserProfileRegistryPath); + if (key is not null && key.GetValue("Languages") is string[] languages) + { + foreach (string language in languages) + { + using RegistryKey subKey = key.OpenSubKey(language); + if (subKey is not null + && subKey.GetValue("TransientLangId") is int transientLangId + && transientLangId == langId) + { + return language; + } + } + } + } + + return CultureInfo.GetCultureInfo((int)langId).Name; + } + #endregion } } From 4f2a951adfe2d1d5d96770f1470184f9dce6d091 Mon Sep 17 00:00:00 2001 From: Yusyuriv Date: Sun, 23 Mar 2025 20:41:28 +0600 Subject: [PATCH 22/22] Small code style changes in keyboard change logic --- Flow.Launcher.Infrastructure/Win32Helper.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index c84c2e3a8a8..7a3a0c36e26 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -464,19 +464,18 @@ private static string GetLanguageTag(uint langId) // https://github.com/dotnet/winforms/pull/8573#issuecomment-1542600949 // // NOTE: this logic may break in future versions of Windows since it is not documented. - if (langId is (int)PInvoke.LOCALE_TRANSIENT_KEYBOARD1 - or (int)PInvoke.LOCALE_TRANSIENT_KEYBOARD2 - or (int)PInvoke.LOCALE_TRANSIENT_KEYBOARD3 - or (int)PInvoke.LOCALE_TRANSIENT_KEYBOARD4) + if (langId is PInvoke.LOCALE_TRANSIENT_KEYBOARD1 + or PInvoke.LOCALE_TRANSIENT_KEYBOARD2 + or PInvoke.LOCALE_TRANSIENT_KEYBOARD3 + or PInvoke.LOCALE_TRANSIENT_KEYBOARD4) { - using RegistryKey key = Registry.CurrentUser.OpenSubKey(UserProfileRegistryPath); - if (key is not null && key.GetValue("Languages") is string[] languages) + using var key = Registry.CurrentUser.OpenSubKey(UserProfileRegistryPath); + if (key?.GetValue("Languages") is string[] languages) { foreach (string language in languages) { - using RegistryKey subKey = key.OpenSubKey(language); - if (subKey is not null - && subKey.GetValue("TransientLangId") is int transientLangId + using var subKey = key.OpenSubKey(language); + if (subKey?.GetValue("TransientLangId") is int transientLangId && transientLangId == langId) { return language;