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

Implement auto-switching to English when the option is enabled #3366

Draft
wants to merge 10 commits into
base: dev
Choose a base branch
from

Conversation

Yusyuriv
Copy link
Member

@Yusyuriv Yusyuriv commented Mar 21, 2025

Warning

DO NOT MERGE YET. Highly experimental, needs thorough testing with both IME and non-IME languages; also needs someone to confirm there are no memory leaks with using the native Windows APIs.

Fixes #2108, fixes #2569, fixes #3126.

What it does

When the Always Start Typing in English Mode option is enabled, when the Flow Launcher window opens, it remembers the current keyboard layout and then switches to English. When the window hides, it restores the remembered keyboard layout.

Testing I've done

  • It correctly switches to English when the window opens and restores the original keyboard layout when the window hides.

Known issues

  • When it gets closed via Esc, Alt+Space, or Enter, it restores the keyboard layout correctly, but when the window loses focus, the layout does not get restored.

Things to consider

  • When used with a language that has an IME mode, don't switch the language, just disable the IME mode.

@Yusyuriv Yusyuriv added the bug Something isn't working label Mar 21, 2025
@Yusyuriv Yusyuriv self-assigned this Mar 21, 2025
@prlabeler prlabeler bot added the enhancement New feature or request label Mar 21, 2025
@Yusyuriv Yusyuriv marked this pull request as draft March 21, 2025 18:43

This comment has been minimized.

Copy link

gitstream-cm bot commented Mar 21, 2025

🥷 Code experts: onesounds, Jack251970

onesounds, Jack251970 have most 👩‍💻 activity in the files.
onesounds, Jack251970 have most 🧠 knowledge in the files.

See details

Flow.Launcher/ViewModel/MainViewModel.cs

Activity based on git-commit:

onesounds Jack251970
MAR 236 additions & 183 deletions 116 additions & 156 deletions
FEB 23 additions & 25 deletions 63 additions & 21 deletions
JAN 17 additions & 21 deletions
DEC 59 additions & 63 deletions
NOV 39 additions & 15 deletions
OCT

Knowledge based on git-blame:
onesounds: 14%
Jack251970: 12%

To learn more about /:\ gitStream - Visit our Docs

Copy link

gitstream-cm bot commented Mar 21, 2025

Be a legend 🏆 by adding a before and after screenshot of the changes you made, especially if they are around UI/UX.

Copy link
Contributor

coderabbitai bot commented Mar 21, 2025

📝 Walkthrough

Walkthrough

This pull request introduces a new helper class to manage keyboard layouts on Windows systems. The KeyboardLayoutHelper static class provides methods for switching to an English keyboard layout and reverting to the previously active layout. Additionally, the MainViewModel has been updated to invoke these methods based on the StartWithEnglishMode setting when the main window is shown or hidden.

Changes

File Change Summary
Flow.Launcher/.../KeyboardLayoutHelper.cs Added a new static class KeyboardLayoutHelper with methods: SetEnglishKeyboardLayout (to switch to an English layout using Windows API calls and store the current layout) and SetPreviousKeyboardLayout (to revert back to the stored keyboard layout). Also added a private method FindEnglishKeyboardLayout to search for an English layout.
Flow.Launcher/.../MainViewModel.cs Modified the Show and Hide methods to call KeyboardLayoutHelper.SetEnglishKeyboardLayout and KeyboardLayoutHelper.SetPreviousKeyboardLayout respectively when the StartWithEnglishMode option is enabled.

Sequence Diagram(s)

sequenceDiagram
    participant MV as MainViewModel
    participant K as KeyboardLayoutHelper
    participant API as Windows API

    MV->>K: On Show() - if StartWithEnglishMode enabled
    K->>API: Retrieve installed keyboard layouts  
    API-->>K: Return layout list
    K->>API: Activate English layout & store current layout
    MV->>K: On Hide() - if StartWithEnglishMode enabled
    K->>API: Restore previous keyboard layout
Loading

Assessment against linked issues

Objective Addressed Explanation
Automatically switch to English keyboard layout on launcher launch and revert on hide ([#2108, #2569, #3126])

Possibly related PRs

  • Add System Language Item #3161: Involves modifications to the KeyboardLayoutHelper class, addressing similar functionalities for managing keyboard layouts.

Suggested reviewers

  • jjw24

Poem

I hop through code in the midnight glow,
Switching layouts swiftly, watch me go!
Keys click in rhythm with a clever art,
Returning to old beats when we must depart.
A bunny’s code delight, as smooth as a flow.
🐇✨
Happy hops in every line!

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (6)
Flow.Launcher/Helper/KeyboardLayoutHelper.cs (4)

1-66: Good implementation of Windows APIs for keyboard layout management

The implementation correctly uses P/Invoke to access the necessary Windows APIs for keyboard layout management. The FindEnglishKeyboardLayout method effectively retrieves and filters keyboard layouts to identify English ones.

However, the current language detection method could be improved:

Consider using explicit English language IDs instead of string comparison for more robust language detection. The current approach using string contains "english" might not work reliably across all Windows versions and language configurations.

-    private const string LAYOUT_ENGLISH_SEARCH = "english";
+    // English language ID (0x0409 = US English, 0x0809 = UK English, etc.)
+    private static readonly uint[] ENGLISH_LANGUAGE_IDS = { 0x0409, 0x0809, 0x0c09, 0x1009, 0x1409, 0x1809, 0x1c09, 0x2009, 0x2409, 0x2809, 0x2c09, 0x3009, 0x3409 };

     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))
+            // Check if it's an English language ID
+            if (Array.IndexOf(ENGLISH_LANGUAGE_IDS, langId) >= 0)
             {
                 return layout;
             }
         }

         return IntPtr.Zero;
     }

68-87: Consider adding thread safety to the layout switching mechanism

The _previousLayout static field is shared and could cause race conditions if layout switching is triggered from multiple threads simultaneously.

Add thread synchronization to prevent potential race conditions:

-    private static IntPtr _previousLayout;
+    private static IntPtr _previousLayout;
+    private static readonly object _layoutLock = new object();

     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);
+        lock (_layoutLock)
+        {
+            // 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);
+
+            // Switch to English layout
+            ActivateKeyboardLayout(englishLayout, 0);
+        }
     }

Also, the bitwise AND operation with 0xFFFF might be unnecessary when storing the layout handle. This operation extracts only the language ID portion but discards the keyboard type information.


89-94: Apply thread safety to SetPreviousKeyboardLayout method as well

Similar to the SetEnglishKeyboardLayout method, this method should also be thread-safe.

     public static void SetPreviousKeyboardLayout()
     {
-        if (_previousLayout == IntPtr.Zero) return;
-        ActivateKeyboardLayout(_previousLayout, 0);
-        _previousLayout = IntPtr.Zero;
+        lock (_layoutLock)
+        {
+            if (_previousLayout == IntPtr.Zero) return;
+            ActivateKeyboardLayout(_previousLayout, 0);
+            _previousLayout = IntPtr.Zero;
+        }
     }

7-95: Add error handling and logging for diagnostics

Since this is an experimental feature and involves Windows API calls that might fail, consider adding error handling and logging.

Add try-catch blocks and logging to help with debugging:

     public static void SetEnglishKeyboardLayout()
     {
+        try
+        {
             // 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);
+        }
+        catch (Exception ex)
+        {
+            // Log the exception but don't crash the application
+            Log.Error("KeyboardLayoutHelper", $"Error switching to English layout: {ex.Message}", ex);
+        }
     }

     public static void SetPreviousKeyboardLayout()
     {
+        try
+        {
             if (_previousLayout == IntPtr.Zero) return;
             ActivateKeyboardLayout(_previousLayout, 0);
             _previousLayout = IntPtr.Zero;
+        }
+        catch (Exception ex)
+        {
+            // Log the exception but don't crash the application
+            Log.Error("KeyboardLayoutHelper", $"Error restoring previous layout: {ex.Message}", ex);
+        }
     }

Also consider adding a simple way to detect if an English layout is already active to avoid unnecessary switching:

public static bool IsEnglishLayoutActive()
{
    try
    {
        var hwnd = GetForegroundWindow();
        var threadId = GetWindowThreadProcessId(hwnd, IntPtr.Zero);
        var currentLayout = GetKeyboardLayout(threadId);
        var langId = (uint)currentLayout.ToInt32() & 0xFFFF;
        
        // Check if it's already an English language ID
        return Array.IndexOf(ENGLISH_LANGUAGE_IDS, langId) >= 0;
    }
    catch (Exception ex)
    {
        Log.Error("KeyboardLayoutHelper", $"Error checking if English layout is active: {ex.Message}", ex);
        return false;
    }
}
Flow.Launcher/ViewModel/MainViewModel.cs (2)

1377-1382: Good integration of keyboard layout switching on window show

The implementation correctly calls KeyboardLayoutHelper.SetEnglishKeyboardLayout() when StartWithEnglishMode is true, which fulfills the PR objective.

Consider adding a check to avoid unnecessary layout switching if English is already the active layout:

                if (StartWithEnglishMode)
                {
+                   // Only switch if not already using English layout
+                   if (!KeyboardLayoutHelper.IsEnglishLayoutActive())
                    KeyboardLayoutHelper.SetEnglishKeyboardLayout();
                }

This would require adding the IsEnglishLayoutActive() method to KeyboardLayoutHelper as suggested in the previous comment.


1377-1455: Consider adding exception handling for keyboard layout operations

Since keyboard layout operations involve native API calls that could potentially fail, adding try-catch blocks would make the code more robust.

Add try-catch blocks around keyboard layout operations to prevent exceptions from affecting the main application flow:

                if (StartWithEnglishMode)
                {
+                   try
+                   {
                        KeyboardLayoutHelper.SetEnglishKeyboardLayout();
+                   }
+                   catch (Exception ex)
+                   {
+                       Log.Error("MainViewModel", $"Error setting English keyboard layout: {ex.Message}", ex);
+                       // Continue execution despite the error
+                   }
                }

And similarly for the hide method:

            if (StartWithEnglishMode)
            {
+               try
+               {
                    KeyboardLayoutHelper.SetPreviousKeyboardLayout();
+               }
+               catch (Exception ex)
+               {
+                   Log.Error("MainViewModel", $"Error restoring previous keyboard layout: {ex.Message}", ex);
+                   // Continue execution despite the error
+               }
            }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dda008f and 81a4632.

📒 Files selected for processing (2)
  • Flow.Launcher/Helper/KeyboardLayoutHelper.cs (1 hunks)
  • Flow.Launcher/ViewModel/MainViewModel.cs (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: gitStream workflow automation
  • GitHub Check: gitStream workflow automation
  • GitHub Check: gitStream.cm
  • GitHub Check: gitStream.cm
  • GitHub Check: gitStream.cm
  • GitHub Check: gitStream.cm
  • GitHub Check: gitStream.cm
🔇 Additional comments (2)
Flow.Launcher/ViewModel/MainViewModel.cs (2)

1451-1455: Good implementation of layout restoration on window hide

The code appropriately restores the previous keyboard layout when hiding the window, fulfilling the PR objective.


17-17: Appropriate namespace inclusion

The using directive for Flow.Launcher.Helper is correctly added to access the new KeyboardLayoutHelper class.

This comment has been minimized.

This comment has been minimized.

@Jack251970
Copy link
Contributor

I have no idea how to test it. So could you please test it to check if PInvoke introduction has broken sth?

@Yusyuriv
Copy link
Member Author

I have no idea how to test it. So could you please test it to check if PInvoke introduction has broken sth?

Yes, with this version the keyboard layout switches to English, but seems to never switch back.

@Yusyuriv
Copy link
Member Author

Yusyuriv commented Mar 22, 2025

To test it, change your keyboard layout to a non-English one, then open Flow. It should switch to English. Close Flow. It should switch to the orignial layout.

@Jack251970
Copy link
Contributor

Get it

This comment has been minimized.

@Jack251970
Copy link
Contributor

Fixed. Now it works well.

This comment has been minimized.

This comment has been minimized.

This comment has been minimized.

This comment has been minimized.

This comment has been minimized.

Copy link

@check-spelling-bot Report

🔴 Please review

See the 📂 files view, the 📜action log, or 📝 job summary for details.

❌ Errors Count
❌ forbidden-pattern 22
⚠️ ignored-expect-variant 1
⚠️ non-alpha-in-dictionary 19

See ❌ Event descriptions for more information.

If the flagged items are 🤯 false positives

If items relate to a ...

  • binary file (or some other file you wouldn't want to check at all).

    Please add a file path to the excludes.txt file matching the containing file.

    File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.

    ^ refers to the file's path from the root of the repository, so ^README\.md$ would exclude README.md (on whichever branch you're using).

  • well-formed pattern.

    If you can write a pattern that would match it,
    try adding it to the patterns.txt file.

    Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.

    Note that patterns can't match multiline strings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
10 min review bug Something isn't working enhancement New feature or request
Projects
None yet
2 participants