4using System.ComponentModel;
5using System.Diagnostics;
8using System.Threading.Tasks;
10using System.Windows.Controls;
11using System.Windows.Input;
12using System.Windows.Media;
13using System.Windows.Threading;
14using Dreamine.UI.Abstractions.VirtualKeyboard;
79 private TaskPoolGlobalHook
_hook =
null!;
210 private static readonly TimeSpan
_guard = TimeSpan.FromMilliseconds(350);
291 #region Dependency Property
344 private static void OnLayoutChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
348 var layout = (VkLayout)e.NewValue;
351 vk._inputModeBtn =
null;
353 vk.IsPasswordLayout = layout == VkLayout.Password;
355 vk.Dispatcher.BeginInvoke(() =>
357 vk.RefreshKeyboardLayout();
358 vk.EnsurePreviewFocus();
359 vk.TryTogglePreviewVisuals();
360 }, DispatcherPriority.Loaded);
366 #region IsPasswordLayout (Readonly DP)
431 vk.TryTogglePreviewVisuals();
513 Interval = TimeSpan.FromMilliseconds(160),
530 #region Private Method
542 if (
_layoutRoot is
null && FindName(
"KeyboardLayoutRoot") is Panel layoutRoot)
585 _hook ??=
new TaskPoolGlobalHook();
596 _ = Task.Run(async () =>
600 await
_hook.RunAsync().ConfigureAwait(
false);
604 Debug.WriteLine($
"[VK Hook] RunAsync failed: {ex.Message}");
662 foreach (var keyButton
in _keys)
664 var useShift =
Layout == VkLayout.Text ||
Layout == VkLayout.Password;
681 _layoutRoot = FindName(
"KeyboardLayoutRoot") as Panel;
683 if (
Layout == VkLayout.Password)
685 if (
_layoutRoot?.FindFirstVisualChild<PasswordBox>(
"VkbPasswordBox") is { } pb && !pb.IsKeyboardFocusWithin)
692 if (
_layoutRoot?.FindFirstVisualChild<TextBox>(
"VkbTextBox") is { } tb && !tb.IsKeyboardFocusWithin)
695 tb.CaretIndex = tb.Text?.Length ?? 0;
712 _langBtn.Visibility = Visibility.Collapsed;
742 var imeModeChanged =
false;
750 imeModeChanged =
true;
759 return imeModeChanged;
812 if (!IsVisible || DesignerProperties.GetIsInDesignMode(
this))
827 SetLanguage(InputLanguageManager.Current.CurrentInputLanguage.Name);
861 if (
Layout != VkLayout.Text &&
Layout != VkLayout.Password)
868 if (!HangulComposer.IsComposableJamo(text))
924 if (
Layout == VkLayout.Password)
927 var keep = Math.Max(0, password.Length - replaceCount);
935 var currentText = textBox.Text ??
string.Empty;
936 var selectionStart = Math.Clamp(textBox.SelectionStart, 0, currentText.Length);
937 var selectionLength = Math.Clamp(textBox.SelectionLength, 0, currentText.Length - selectionStart);
939 if (selectionLength > 0)
941 textBox.SelectedText = text;
942 textBox.CaretIndex = selectionStart + text.Length;
946 var removeStart = Math.Max(0, selectionStart - replaceCount);
947 removeStart = Math.Min(removeStart, currentText.Length);
948 var removeLength = Math.Clamp(selectionStart - removeStart, 0, currentText.Length - removeStart);
949 textBox.Text = currentText.Remove(removeStart, removeLength).Insert(removeStart, text);
950 textBox.CaretIndex = removeStart + text.Length;
953 textBox.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
974 if (
Layout == VkLayout.Password)
980 var caret = Math.Clamp(textBox.CaretIndex, 0, textBox.Text.Length);
981 return textBox.Text[..caret];
1003 passwordBox.Password = password;
1024 var window = Window.GetWindow(
this) ??
this as DependencyObject;
1025 return window ==
null ? null : FindByNameInVisualTree<TextBox>(window,
"VkbTextBox");
1046 var window = Window.GetWindow(
this) ??
this as DependencyObject;
1047 return window ==
null ? null : FindByNameInVisualTree<PasswordBox>(window,
"VkbPasswordBox");
1100 if (!Dispatcher.CheckAccess())
1148 _ = Task.Run(async () =>
1152 await Task.Delay(400, token).ConfigureAwait(
false);
1153 while (!token.IsCancellationRequested)
1156 await Task.Delay(50, token).ConfigureAwait(
false);
1159 catch (OperationCanceledException)
1208 char[] hiddenChars =
1218 return new string(lang.Where(c => !hiddenChars.Contains(c)).ToArray()).Trim();
1239 if (
string.IsNullOrEmpty(lang))
1241 lang = InputLanguageManager.Current.CurrentInputLanguage.Name;
1246 var currentLang = lang
switch
1248 "ko-KR" => LanguageCode.ko_KR,
1249 "vi-VN" => LanguageCode.vi_VN,
1250 "zh-CN" => LanguageCode.zh_CN,
1251 _ => LanguageCode.en_US
1292 var targetName = useKorean ?
"ko-KR" :
"en-US";
1295 var available = InputLanguageManager.Current.AvailableInputLanguages;
1296 var match = available?
1297 .Cast<System.Globalization.CultureInfo>()
1298 .FirstOrDefault(c =>
string.Equals(c.Name, targetName, StringComparison.OrdinalIgnoreCase));
1302 InputLanguageManager.Current.CurrentInputLanguage = match;
1316 catch (Exception ex)
1318 Debug.WriteLine($
"[VK] SetKoreanEnglishMode failed: {ex.Message}");
1343 if (currentImeMode != useKorean)
1349 case LanguageCode.ko_KR:
1353 case LanguageCode.zh_CN:
1385 var currentLanguage = InputLanguageManager.Current.CurrentInputLanguage.Name;
1386 var isKoreanLanguage =
string.Equals(
1389 StringComparison.OrdinalIgnoreCase);
1413 DispatcherTimer timer =
new() { Interval = TimeSpan.FromMilliseconds(100) };
1414 timer.Tick += (_, __) =>
1420 if (--remaining <= 0)
1451 readSystemIme =
false;
1490 var window = Window.GetWindow(
this) ??
this as DependencyObject;
1491 if (window ==
null)
return;
1493 var vkbText = FindByNameInVisualTree<TextBox>(window,
"VkbTextBox");
1494 var vkbPwd = FindByNameInVisualTree<PasswordBox>(window,
"VkbPasswordBox");
1496 if (vkbText !=
null)
1497 vkbText.Visibility =
IsPasswordLayout ? Visibility.Collapsed : Visibility.Visible;
1500 vkbPwd.Visibility =
IsPasswordLayout ? Visibility.Visible : Visibility.Collapsed;
1545 if (root is T fe && fe.Name == name)
1548 int count = VisualTreeHelper.GetChildrenCount(root);
1549 for (
int i = 0; i < count; i++)
1551 var child = VisualTreeHelper.GetChild(root, i);
1553 if (found !=
null)
return found;
1560 #region Event Handler
1629 if (DesignerProperties.GetIsInDesignMode(
this))
1632 RemoveHandler(
Key.ClickEvent, (RoutedEventHandler)
KeyClick);
1633 AddHandler(
Key.ClickEvent, (RoutedEventHandler)
KeyClick);
1635 Dispatcher.BeginInvoke(() =>
1641 }, DispatcherPriority.SystemIdle);
1700 if (e.NewValue is not
bool isVisible)
return;
1746 var keyCode = e.Data.KeyCode;
1747 Dispatcher.BeginInvoke(() =>
1749 var refreshLayout =
false;
1751 refreshLayout =
true;
1755 case KeyCode.VcLeftShift:
1756 case KeyCode.VcRightShift:
1760 if (
Layout == VkLayout.Text ||
Layout == VkLayout.Password)
1762 if (
_keys?.FirstOrDefault(k => k.KeyCode == KeyCode.VcLeftShift) is { } shiftKey)
1763 shiftKey.IsPressed =
true;
1764 refreshLayout =
true;
1767 case KeyCode.VcCapsLock:
1769 if (
_keys?.FirstOrDefault(k => k.KeyCode == KeyCode.VcCapsLock) is { } capsLockKey)
1771 refreshLayout =
true;
1773 case KeyCode.VcLeftControl:
1774 case KeyCode.VcRightControl:
1775 if (
Layout == VkLayout.Text ||
Layout == VkLayout.Password)
1777 if (
_keys?.FirstOrDefault(k => k.KeyCode == KeyCode.VcLeftControl) is { } ctrlKey)
1778 ctrlKey.IsPressed =
true;
1781 if (keyCode >= KeyCode.VcA && keyCode <= KeyCode.VcZ)
1784 if (
_keys?.FirstOrDefault(k => k.KeyCode == keyCode) is { } keyBtn)
1785 keyBtn.IsPressed =
true;
1791 }, DispatcherPriority.ApplicationIdle);
1820 var keyCode = e.Data.KeyCode;
1821 Dispatcher.BeginInvoke(() =>
1825 case KeyCode.VcLeftShift:
1826 case KeyCode.VcRightShift:
1830 if (
_keys?.FirstOrDefault(k => k.KeyCode == KeyCode.VcLeftShift) is { } shiftKey)
1831 shiftKey.IsPressed =
false;
1835 case KeyCode.VcCapsLock:
1836 if (
_keys?.FirstOrDefault(k => k.KeyCode == KeyCode.VcCapsLock) is { } capsLockKey)
1840 case KeyCode.VcLeftControl:
1841 case KeyCode.VcRightControl:
1843 if (
_keys?.FirstOrDefault(k => k.KeyCode == KeyCode.VcLeftControl) is { } ctrlKey)
1844 ctrlKey.IsPressed =
false;
1847 if (
_keys?.FirstOrDefault(k => k.KeyCode == keyCode) is { } keyBtn)
1848 keyBtn.IsPressed =
false;
1851 }, DispatcherPriority.ApplicationIdle);
1966 private void KeyClick(
object? sender, RoutedEventArgs e)
1968 if (
Layout != VkLayout.Text &&
Layout != VkLayout.Password)
1980 if (e.OriginalSource is Button { Name:
"_langBtn" })
1987 if (e.OriginalSource is Button { Name:
"_inputModeBtn" })
1994 if (e.OriginalSource is not
Key key)
1997 var keyCode = key.KeyCode;
2003 case KeyCode.VcLeftShift:
2004 case KeyCode.VcRightShift:
2011 case KeyCode.VcCapsLock:
2014 DispatcherTimer tCaps =
new() { Interval = TimeSpan.FromMilliseconds(120) };
2015 tCaps.Tick += (_, __) =>
2024 case KeyCode.VcLeftControl:
2031 case KeyCode.VcBackspace:
2036 case KeyCode.VcEnter:
2047 case KeyCode.VcSpace:
2060 if (
_keys?.FirstOrDefault(k => k.KeyCode == KeyCode.VcLeftControl) is { } ctrlKey)
2061 ctrlKey.IsPressed =
false;
2131 RemoveHandler(
Key.ClickEvent, (RoutedEventHandler)
KeyClick);
2144 catch (Exception ex) { Debug.WriteLine($
"[VK] Dispose error: {ex.Message}"); }
2147 GC.SuppressFinalize(
this);
volatile bool _hookSubscribed
void ReplaceTextTail(int replaceCount, string text)
void KeyboardUserControl_Unloaded(object? sender, RoutedEventArgs e)
void SynchronizeKeyboardState(object? sender, EventArgs e)
void UnregisterHookEvents()
void Hook_MouseReleased(object? sender, MouseHookEventArgs e)
DateTime _pendingKoreanInputModeUntil
void RecheckKeyboardStateSoon(bool useKorean)
readonly HangulComposer _hangulComposer
bool? _pendingKoreanInputMode
virtual void Dispose(bool disposing)
void OnInputLanguageChanged(object? sender, InputLanguageEventArgs e)
void SetLanguage(string lang)
void KeyboardUserControl_Loaded(object? sender, RoutedEventArgs e)
void EnsurePreviewFocus()
CancellationTokenSource? _repeatBackspaceCts
void ReleasePressedKeys()
void KeyboardUserControl_IsVisibleChanged(object? sender, DependencyPropertyChangedEventArgs e)
EventHandler< LanguageCode >? OnKeyboardLanguageChanged
string NormalizeLanguageString(string lang)
void BackspaceMouseCaptureLost(object? sender, MouseEventArgs e)
void KeyClick(object? sender, RoutedEventArgs e)
void Hook_KeyPressed(object? sender, KeyboardHookEventArgs e)
static readonly RoutedEvent VirtualKeyDownEvent
IEnumerable< Key >? _keys
void BackspacePointerReleased(object? sender, InputEventArgs e)
RoutedEventHandler VirtualKeyDown
void SimulateBackspaceStroke()
bool IsKoreanInputActive()
static readonly DependencyProperty LayoutProperty
volatile bool _hookRunning
bool TryHandleComposedTextKey(Key key)
void RefreshKeyboardVisualState(bool readSystemIme=true)
DateTime _lastSelfLangSwitchAt
string GetTextBeforeCaret()
async Task RegisterHookEventsAsync()
static readonly TimeSpan _guard
void TryTogglePreviewVisuals()
readonly EventSimulator _eventSimulator
void StartBackspaceRepeat(Key key)
void RefreshKeyboardLayout(string lang="")
TextBox? VkbTextBoxFromTree()
static void OnLayoutChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
DateTime _lastSelfImeToggleAt
void Hook_KeyReleased(object? sender, KeyboardHookEventArgs e)
bool SetKoreanEnglishMode(bool useKorean)
static ? T FindByNameInVisualTree< T >(DependencyObject root, string name)
void ReleaseBackspaceRepeat()
DateTime _lastSelfShiftAt
~DreamineVirtualKeyboard()
void InsertRawText(string text)
PasswordBox? VkbPasswordBoxFromTree()
DreamineVirtualKeyboard()
void SimulateCharKey(Key key)
void SetPassword(string password)
static void OnIsPasswordLayoutChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
static readonly DependencyProperty IsPasswordLayoutProperty
readonly DispatcherTimer _keyboardStateSyncTimer
bool UpdateInputModeKey(bool readSystemIme=true)
bool HasPendingKoreanInputMode
static readonly DependencyPropertyKey IsPasswordLayoutPropertyKey
void ApplyDesiredImeMode(bool useKorean)
static bool SetImeMode(bool native)
static short GetKeyState(int keyCode)
string GetDisplayText(bool shift, bool capsLock, LanguageCode languageCode, bool imeMode)