From a7d36ab2762e882f74c5f7ccb8d3977963ee58c1 Mon Sep 17 00:00:00 2001 From: tombogle Date: Fri, 23 May 2025 09:31:47 -0400 Subject: [PATCH 1/6] +semver:minor Added KeysExtensions class with the IsNavigationKey extension method. Fixed a subtle bug in IbusKeyboardSwitchingAdaptor when determining whether IBus would have handled a key event while a pre-edit is active. The code now accounts for the possibility of modifier keys (particularly Ctrl), which IBus would presumably have handled when in combination with navigation keys, Backspace, and Delete. --- CHANGELOG.md | 8 ++ Palaso.sln.DotSettings | 1 + .../Linux/IbusKeyboardSwitchingAdaptor.cs | 123 ++++++++++-------- .../Extensions/KeysExtensions.cs | 35 +++++ .../BetterGrid/CalendarEditingControl.cs | 25 +--- 5 files changed, 117 insertions(+), 75 deletions(-) create mode 100644 SIL.Windows.Forms/Extensions/KeysExtensions.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ecfb1796..08f2afd6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added + +- [SIL.Windows.Forms] Added KeysExtensions class with the IsNavigationKey extension method. + +### Fixed + +- [SIL.Windows.Kwyboarding] Fixed a subtle bug in IbusKeyboardSwitchingAdaptor when determining whether IBus would have handled a key event while a pre-edit is active. The code now accounts for the possibility of modifier keys (particularly Ctrl), which IBus would presumably have handled when in combination with navigation keys, Backspace, and Delete. +- ## [16.0.0] - 2025-05-20 ### Added diff --git a/Palaso.sln.DotSettings b/Palaso.sln.DotSettings index b94e70997..a048e430c 100644 --- a/Palaso.sln.DotSettings +++ b/Palaso.sln.DotSettings @@ -6,6 +6,7 @@ GUI GUID IMDI + IME ISO LDML LIFT diff --git a/SIL.Windows.Forms.Keyboarding/Linux/IbusKeyboardSwitchingAdaptor.cs b/SIL.Windows.Forms.Keyboarding/Linux/IbusKeyboardSwitchingAdaptor.cs index 7ceba8146..e577931a9 100644 --- a/SIL.Windows.Forms.Keyboarding/Linux/IbusKeyboardSwitchingAdaptor.cs +++ b/SIL.Windows.Forms.Keyboarding/Linux/IbusKeyboardSwitchingAdaptor.cs @@ -100,7 +100,7 @@ protected virtual void SelectKeyboard(KeyboardDescription keyboard) context.SetEngine(ibusKeyboard.IBusKeyboardEngine.LongName); } - private void SetImePreeditWindowLocationAndSize(Control control) + private void SetImePreEditWindowLocationAndSize(Control control) { var eventHandler = GetEventHandlerForControl(control); if (eventHandler == null) @@ -165,8 +165,7 @@ private void OnControlRegistered(object sender, RegisterEventArgs e) e.Control.PreviewKeyDown += HandlePreviewKeyDown; e.Control.KeyPress += HandleKeyPress; - var scrollableControl = e.Control as ScrollableControl; - if (scrollableControl != null) + if (e.Control is ScrollableControl scrollableControl) scrollableControl.Scroll += HandleScroll; } @@ -182,8 +181,7 @@ private void OnControlRemoving(object sender, ControlEventArgs e) e.Control.KeyPress -= HandleKeyPress; e.Control.KeyDown -= HandleKeyDownAfterIbusHandledKey; - var scrollableControl = e.Control as ScrollableControl; - if (scrollableControl != null) + if (e.Control is ScrollableControl scrollableControl) scrollableControl.Scroll -= HandleScroll; var eventHandler = GetEventHandlerForControl(e.Control); @@ -202,7 +200,7 @@ private void OnControlRemoving(object sender, ControlEventArgs e) /// /// Passes the key event to ibus. This method deals with the special keys (Cursor up/down, - /// backspace etc) that usually shouldn't cause a commit. + /// backspace, etc.) that usually shouldn't cause a commit. /// private bool PassSpecialKeyEventToIbus(Control control, Keys keyChar, Keys modifierKeys) { @@ -212,8 +210,11 @@ private bool PassSpecialKeyEventToIbus(Control control, Keys keyChar, Keys modif private bool PassKeyEventToIbus(Control control, char keyChar, Keys modifierKeys) { - if (keyChar == 0x7f) // we get this for Ctrl-Backspace - keyChar = '\b'; + const char asciiDel = (char)0x7F; + const char backspace = '\b'; + + if (keyChar == asciiDel) // Ctrl+Backspace arrives as DEL; normalize to Backspace + keyChar = backspace; return PassKeyEventToIbus(control, keyChar, modifierKeys, true); } @@ -235,7 +236,7 @@ private bool PassKeyEventToIbus(Control control, int keySym, Keys modifierKeys, if (resetIfUnhandled) { - // If ProcessKeyEvent doesn't consume the key, we need to kill any preedits and + // If ProcessKeyEvent doesn't consume the key, we need to kill any pre-edits and // sync before continuing processing the keypress. We return false so that the // control can process the character. ResetAndWaitForCommit(control); @@ -254,8 +255,7 @@ private void HandleGotFocus(object sender, EventArgs e) // and the other time because we have to call the original window proc. However, only // the second time will the control report as being focused (or when we not intercept // the message then the first time) (see SimpleRootSite.OriginalWndProc). - var control = sender as Control; - if (control == null || !control.Focused) + if (!(sender is Control control) || !control.Focused) return; _ibusComm.FocusIn(); @@ -276,7 +276,7 @@ private void HandleLostFocus(object sender, EventArgs e) /// /// Inform input bus of Keydown events - /// This is useful to get warning of key that should stop the preedit + /// This is useful to get warning of key that should stop the pre-edit /// private void HandlePreviewKeyDown(object sender, PreviewKeyDownEventArgs e) { @@ -290,49 +290,62 @@ private void HandlePreviewKeyDown(object sender, PreviewKeyDownEventArgs e) if (_needIMELocation) { - SetImePreeditWindowLocationAndSize(control); + SetImePreEditWindowLocationAndSize(control); _needIMELocation = false; } var key = e.KeyCode; - switch (key) + if (key == Keys.Escape) { - case Keys.Escape: - // These should end a preedit, so wait until that has happened - // before allowing the key to be processed. - ResetAndWaitForCommit(control); - return; - case Keys.Up: - case Keys.Down: - case Keys.Left: - case Keys.Right: - case Keys.Delete: - case Keys.PageUp: - case Keys.PageDown: - case Keys.Home: - case Keys.End: - case Keys.Back: - if (PassSpecialKeyEventToIbus(control, key, e.Modifiers)) - { - // If IBus handled the key we don't want the control to get it. However, - // we can't do this in PreviewKeyDown, so we temporarily subscribe to - // KeyDown and suppress the key event there. - control.KeyDown += HandleKeyDownAfterIbusHandledKey; - } - return; + // These should end a pre-edit, so wait until that has happened + // before allowing the key to be processed. + ResetAndWaitForCommit(control); + return; } + + if (IsKeyHandledByIbus(key)) + { + if (PassSpecialKeyEventToIbus(control, key, e.Modifiers)) + { + // If IBus handled the key we don't want the control to get it. However, + // we can't do this in PreviewKeyDown, so we temporarily subscribe to + // KeyDown and suppress the key event there. + control.KeyDown += HandleKeyDownAfterIbusHandledKey; + } + return; + } + // pass function keys onto ibus since they don't appear (on mono at least) as WM_SYSCHAR if (key >= Keys.F1 && key <= Keys.F24) PassSpecialKeyEventToIbus(control, key, e.Modifiers); } /// - /// Handles a key down. While a preedit is active we don't want the control to handle + /// Handles a key down. While a pre-edit is active we don't want the control to handle /// any of the keys that IBus deals with. /// private void HandleKeyDownAfterIbusHandledKey(object sender, KeyEventArgs e) { - switch (e.KeyCode) + if (IsKeyHandledByIbus(e.KeyCode) && sender is Control control) + { + var eventHandler = GetEventHandlerForControl(control); + if (eventHandler != null) + e.SuppressKeyPress = eventHandler.IsPreeditActive; + control.KeyDown -= HandleKeyDownAfterIbusHandledKey; + } + } + + /// + /// Gets whether the given key is expected to be handled by IBus when a pre-edit is active. + /// + /// + /// REVIEW: During pre-edit, I assume that IBus handles both basic and modified navigation + /// keys like Ctrl+Left. ChatGPT says this is true, but I have not been able to verify this + /// since I don't know how to set up a Linux/IBus environment where I could test this. + /// + private bool IsKeyHandledByIbus(Keys key) + { + switch (key & Keys.KeyCode) { case Keys.Up: case Keys.Down: @@ -344,13 +357,10 @@ private void HandleKeyDownAfterIbusHandledKey(object sender, KeyEventArgs e) case Keys.Home: case Keys.End: case Keys.Back: - var control = sender as Control; - var eventHandler = GetEventHandlerForControl(control); - if (eventHandler != null) - e.SuppressKeyPress = eventHandler.IsPreeditActive; - control.KeyDown -= HandleKeyDownAfterIbusHandledKey; - break; + return true; } + + return false; } /// @@ -360,17 +370,20 @@ private void HandleKeyDownAfterIbusHandledKey(object sender, KeyEventArgs e) /// this method gets called. We forward the key press to IBus. If IBus swallowed the key /// it will return true, so no further handling is done by the control, otherwise the /// control will process the key and update the selection. - /// If IBus swallows the key event, it will either raise the UpdatePreeditText event, - /// allowing the event handler to insert the composition as preedit (and update the - /// selection), or it will raise the CommitText event so that the event handler can - /// remove the preedit, replace it with the final composition string and update the - /// selection. Some IBus keyboards might raise a ForwardKeyEvent (handled by - /// ) prior to calling CommitText to - /// simulate a key press (e.g. backspace) so that the event handler can modify the - /// existing text of the control. + /// If IBus swallows the key event, it will either raise the + /// event, allowing the event handler to + /// insert the composition as pre-edit (and update the selection), or it will raise the + /// event so that the event handler can + /// remove the pre-edit, replace it with the final composition string and update the + /// selection. Some IBus keyboards might raise a + /// (handled by + /// ) prior to raising + /// to simulate a key press (e.g. backspace) + /// so that the event handler can modify the existing text of the control. /// IBus might also open a pop-up window at the location we told it /// () to display possible - /// compositions. However, it will still call UpdatePreeditText. + /// compositions. However, it will still raise + /// . private void HandleKeyPress(object sender, KeyPressEventArgs e) { e.Handled = PassKeyEventToIbus(sender as Control, e.KeyChar, Control.ModifierKeys); @@ -390,7 +403,7 @@ private void HandleScroll(object sender, ScrollEventArgs e) if (!_ibusComm.Connected) return; - SetImePreeditWindowLocationAndSize(sender as Control); + SetImePreEditWindowLocationAndSize(sender as Control); } #endregion diff --git a/SIL.Windows.Forms/Extensions/KeysExtensions.cs b/SIL.Windows.Forms/Extensions/KeysExtensions.cs new file mode 100644 index 000000000..2900c56d3 --- /dev/null +++ b/SIL.Windows.Forms/Extensions/KeysExtensions.cs @@ -0,0 +1,35 @@ +using System.Windows.Forms; + +namespace SIL.Windows.Forms.Extensions +{ + public static class KeysExtensions + { + /// + /// Determines if the key is a navigation key. + /// + /// The key to examine + /// If true, the letter `A` will be treated as a + /// navigation key. In contexts where Ctrl-A is not a shortcut for Select All or + /// that behavior is not relevant, pass false or omit this optional parameter. + public static bool IsNavigationKey(this Keys key, bool ctrlPressed = false) + { + switch (key & Keys.KeyCode) + { + case Keys.Left: + case Keys.Up: + case Keys.Down: + case Keys.Right: + case Keys.Home: + case Keys.End: + case Keys.PageDown: + case Keys.PageUp: + return true; + case Keys.A: + return ctrlPressed; + default: + return false; + } + } + } + +} diff --git a/SIL.Windows.Forms/Widgets/BetterGrid/CalendarEditingControl.cs b/SIL.Windows.Forms/Widgets/BetterGrid/CalendarEditingControl.cs index 2469217c1..8818bc85d 100644 --- a/SIL.Windows.Forms/Widgets/BetterGrid/CalendarEditingControl.cs +++ b/SIL.Windows.Forms/Widgets/BetterGrid/CalendarEditingControl.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Drawing; using System.Windows.Forms; +using SIL.Windows.Forms.Extensions; namespace SIL.Windows.Forms.Widgets.BetterGrid { @@ -114,21 +115,8 @@ public void ApplyCellStyleToEditingControl(DataGridViewCellStyle dataGridViewCel /// ------------------------------------------------------------------------------------ public bool EditingControlWantsInputKey(Keys key, bool dataGridViewWantsInputKey) { - // Let the DateTimePicker handle the keys listed. - switch (key & Keys.KeyCode) - { - case Keys.Left: - case Keys.Up: - case Keys.Down: - case Keys.Right: - case Keys.Home: - case Keys.End: - case Keys.PageDown: - case Keys.PageUp: - return true; - default: - return !dataGridViewWantsInputKey; - } + // Let the DateTimePicker handle navigation keys. + return key.IsNavigationKey() || !dataGridViewWantsInputKey; } /// ------------------------------------------------------------------------------------ @@ -138,10 +126,7 @@ public void PrepareEditingControlForEdit(bool selectAll) } /// ------------------------------------------------------------------------------------ - public bool RepositionEditingControlOnValueChange - { - get { return false; } - } + public bool RepositionEditingControlOnValueChange => false; #endregion } From 4242a32f8829b816e8e77ef3324527eeed9ee8ef Mon Sep 17 00:00:00 2001 From: tombogle Date: Fri, 23 May 2025 10:01:33 -0400 Subject: [PATCH 2/6] Improved summary comments for IsNavigationKey --- SIL.Windows.Forms/Extensions/KeysExtensions.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/SIL.Windows.Forms/Extensions/KeysExtensions.cs b/SIL.Windows.Forms/Extensions/KeysExtensions.cs index 2900c56d3..f89ee4caf 100644 --- a/SIL.Windows.Forms/Extensions/KeysExtensions.cs +++ b/SIL.Windows.Forms/Extensions/KeysExtensions.cs @@ -5,12 +5,16 @@ namespace SIL.Windows.Forms.Extensions public static class KeysExtensions { /// - /// Determines if the key is a navigation key. + /// Determines whether the specified key is considered a navigation key. /// - /// The key to examine - /// If true, the letter `A` will be treated as a - /// navigation key. In contexts where Ctrl-A is not a shortcut for Select All or - /// that behavior is not relevant, pass false or omit this optional parameter. + /// The key (or key-combination) to examine. + /// If true, the letter Keys.A will be treated as a + /// navigation key (even if the value does not explicitly include + /// Keys.Control). In contexts where Ctrl-A is not a shortcut for Select All or that + /// behavior is not relevant, pass false or omit this optional parameter. + /// If represents the combination Ctrl-A (i.e., + /// Keys.Control | Keys.A), but is false, + /// this method will return false. public static bool IsNavigationKey(this Keys key, bool ctrlPressed = false) { switch (key & Keys.KeyCode) From 15943d966f507aca5b35a7ebb389134fb349b235 Mon Sep 17 00:00:00 2001 From: tombogle Date: Wed, 9 Jul 2025 21:59:28 -0400 Subject: [PATCH 3/6] Fixed assignment of control variable in HandlePreviewKeyDown so that the compiler doesn't think we're unsure whether the sender will be a Control. --- .../Linux/IbusKeyboardSwitchingAdaptor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SIL.Windows.Forms.Keyboarding/Linux/IbusKeyboardSwitchingAdaptor.cs b/SIL.Windows.Forms.Keyboarding/Linux/IbusKeyboardSwitchingAdaptor.cs index e577931a9..8a8d84eb5 100644 --- a/SIL.Windows.Forms.Keyboarding/Linux/IbusKeyboardSwitchingAdaptor.cs +++ b/SIL.Windows.Forms.Keyboarding/Linux/IbusKeyboardSwitchingAdaptor.cs @@ -283,7 +283,7 @@ private void HandlePreviewKeyDown(object sender, PreviewKeyDownEventArgs e) if (!_ibusComm.Connected) return; - var control = sender as Control; + var control = (Control)sender; var eventHandler = GetEventHandlerForControl(control); if (eventHandler == null) return; From 1b05a19f3f76de34be124da102a0ffa01b74cc91 Mon Sep 17 00:00:00 2001 From: Tom Bogle Date: Thu, 10 Jul 2025 11:10:33 -0400 Subject: [PATCH 4/6] Fixed typo in CHANGELOG.md Co-authored-by: Eberhard Beilharz --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2436a391b..4ada44aba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - [SIL.Windows.Forms] In `CustomDropDown.OnOpening`, fixed check that triggers timer to stop. -- [SIL.Windows.Kwyboarding] Fixed a subtle bug in IbusKeyboardSwitchingAdaptor when determining whether IBus would have handled a key event while a pre-edit is active. The code now accounts for the possibility of modifier keys (particularly Ctrl), which IBus would presumably have handled when in combination with navigation keys, Backspace, and Delete. +- [SIL.Windows.Keyboarding] Fixed a subtle bug in IbusKeyboardSwitchingAdaptor when determining whether IBus would have handled a key event while a pre-edit is active. The code now accounts for the possibility of modifier keys (particularly Ctrl), which IBus would presumably have handled when in combination with navigation keys, Backspace, and Delete. ## [16.0.0] - 2025-05-20 From e195a1307cc2d09cde9749c2d43366301ef77a91 Mon Sep 17 00:00:00 2001 From: tombogle Date: Fri, 20 Mar 2026 10:49:01 -0400 Subject: [PATCH 5/6] Fixed incorrect scope in CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c681ad790..9d500447c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - [SIL.WritingSystems] More fixes to consistently use 繁体中文 and 简体中文 for Traditional and Simplified Chinese native language names, and Chinese (Traditional) and Chinese (Simplified) for their English names. - [SIL.Windows.Forms] Prevent BetterLabel from responding to OnTextChanged when it has been disposed. - [SIL.Windows.Forms] Prevent ContributorsListControl.GetContributionFromRow from throwing an exception when the DataGridView has no valid rows selected. -- [SIL.Windows.Keyboarding] Fixed a subtle bug in IbusKeyboardSwitchingAdaptor when determining whether IBus would have handled a key event while a pre-edit is active. The code now accounts for the possibility of modifier keys (particularly Ctrl), which IBus would presumably have handled when in combination with navigation keys, Backspace, and Delete. +- [SIL.Windows.Forms.Keyboarding] Fixed a subtle bug in IbusKeyboardSwitchingAdaptor when determining whether IBus would have handled a key event while a pre-edit is active. The code now accounts for the possibility of modifier keys (particularly Ctrl), which IBus would presumably have handled when in combination with navigation keys, Backspace, and Delete. ### Changed - [SIL.Windows.Forms.i18n, SIL.Core.Desktop.i18n] BREAKING CHANGE: Move L10NSharpLocalizer from Windows.Forms to Core.Desktop so it can be accessed without Winforms dependency. From ab9ba40b6d534ec308eb9d50e32cf545e702132b Mon Sep 17 00:00:00 2001 From: tombogle Date: Fri, 20 Mar 2026 12:23:08 -0400 Subject: [PATCH 6/6] Removed "bug fix" claim from CHANGELOG since three AI engines agree that this is merely a refactoring, not a bug fix. Added Eberhard's proposed testing steps to the REVIEW comment. --- CHANGELOG.md | 1 - .../Linux/IbusKeyboardSwitchingAdaptor.cs | 26 ++++++++++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7cee8a75..56638bdf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - [SIL.Windows.Forms] Prevent BetterLabel from responding to OnTextChanged when it has been disposed. - [SIL.Windows.Forms] Prevent ContributorsListControl.GetContributionFromRow from throwing an exception when the DataGridView has no valid rows selected. - [SIL.Media] BREAKING CHANGE (subtle and unlikely): WindowsAudioSession.OnPlaybackStopped now passes itself as the sender instead of a private implementation object, making the event arguments correct. -- [SIL.Windows.Forms.Keyboarding] Fixed a subtle bug in IbusKeyboardSwitchingAdaptor when determining whether IBus would have handled a key event while a pre-edit is active. The code now accounts for the possibility of modifier keys (particularly Ctrl), which IBus would presumably have handled when in combination with navigation keys, Backspace, and Delete. ### Changed - [SIL.Windows.Forms.i18n, SIL.Core.Desktop.i18n] BREAKING CHANGE: Move L10NSharpLocalizer from Windows.Forms to Core.Desktop so it can be accessed without Winforms dependency. diff --git a/SIL.Windows.Forms.Keyboarding/Linux/IbusKeyboardSwitchingAdaptor.cs b/SIL.Windows.Forms.Keyboarding/Linux/IbusKeyboardSwitchingAdaptor.cs index 8a8d84eb5..1e89eaec0 100644 --- a/SIL.Windows.Forms.Keyboarding/Linux/IbusKeyboardSwitchingAdaptor.cs +++ b/SIL.Windows.Forms.Keyboarding/Linux/IbusKeyboardSwitchingAdaptor.cs @@ -315,7 +315,7 @@ private void HandlePreviewKeyDown(object sender, PreviewKeyDownEventArgs e) return; } - // pass function keys onto ibus since they don't appear (on mono at least) as WM_SYSCHAR + // pass function keys on to IBus since they don't appear (on Mono at least) as WM_SYSCHAR if (key >= Keys.F1 && key <= Keys.F24) PassSpecialKeyEventToIbus(control, key, e.Modifiers); } @@ -339,9 +339,27 @@ private void HandleKeyDownAfterIbusHandledKey(object sender, KeyEventArgs e) /// Gets whether the given key is expected to be handled by IBus when a pre-edit is active. /// /// - /// REVIEW: During pre-edit, I assume that IBus handles both basic and modified navigation - /// keys like Ctrl+Left. ChatGPT says this is true, but I have not been able to verify this - /// since I don't know how to set up a Linux/IBus environment where I could test this. + /// REVIEW: During pre-edit, IBus presumably handles both basic and modified navigation + /// keys like Ctrl+Left. Multiple AI sources agree this is true, but it has not been + /// verified in an actual Linux/IBus environment. Eberhard has proposed the following + /// testing procedure on Linux: + /// + /// Install Ubuntu 24.04 in a VM. + /// Install an IME that uses pre-edit: in a terminal run + /// sudo apt update && sudo apt install ibus-pinyin + /// Open Keyboard settings, click "+ Add Input Source", search for Pinyin by clicking + /// on the three dots below English. If you type the search term "pinyin" you'll get + /// "Other". Clicking that shows "Chinese (Pinyin)" which you then can add. + /// Close the settings. + /// In the terminal run ibus restart (or reboot). + /// The language/keyboard picker in the top bar should now offer "Chinese (Pinyin)". + /// + /// Open a text field in a SIL application that runs on Linux and uses + /// `KeyboardController` (maybe WeSay), switch to Chinese (Pinyin), and begin typing to + /// enter pre-edit mode. Then press modified navigation keys like Ctrl+Left and verify + /// that IBus handles them correctly (e.g., the pre-edit is committed or cancelled as + /// expected, rather than the key being ignored or causing duplicate input). + /// /// private bool IsKeyHandledByIbus(Keys key) {