private static void _SendSelectedLetterAsKeyPress(Boolean delayInput = false) { var pos = Caret.GetPosition(_sActiveKeyboardWindow); var letter = _sActiveSelectorWindow.SelectedLetter; _HidePopup(); try { if (!SetForegroundWindow(_sActiveKeyboardWindow)) { // Something went wrong, ignore. return; } } catch (Win32Exception e) { // For reasons not yet understood we sometimes get a 0 error code turned into an exception (operation succeeded). if (e.NativeErrorCode != 0) { return; } } var keyboardInput = new KEYBDINPUT(); keyboardInput.wVk = 0; // required by unicode event keyboardInput.wScan = (Int16)letter; keyboardInput.dwFlags = KEYEVENTF.UNICODE; keyboardInput.dwExtraInfo = GetMessageExtraInfo(); keyboardInput.time = 0; var keyDown = new INPUT(); keyDown.type = INPUT_KEYBOARD; keyDown.U.ki = keyboardInput; var keyUp = keyDown; keyUp.U.ki.dwFlags |= KEYEVENTF.KEYUP; // If an error happens here it's probably due to UIPI, i.e. the target application is of higher integrity. // We ignore the error. if (delayInput) { ThreadPool.QueueUserWorkItem(context => { // Just block for a bit. Thread.Sleep(_kReleaseErrorMargin); ((SynchronizationContext)context).Post(_ => { SendInput(1, new[] { keyDown }, Marshal.SizeOf(keyDown)); SendInput(1, new[] { keyUp }, Marshal.SizeOf(keyDown)); }, null); }, SynchronizationContext.Current); } else { SendInput(1, new[] { keyDown, keyUp }, Marshal.SizeOf(keyDown)); SendInput(1, new[] { keyUp }, Marshal.SizeOf(keyDown)); } }
public static Boolean HandleKeyPress(Boolean isDown, LowLevelListener.KeyHookEventArgs e) { if (!_sEnabled) { return(false); } // If we get here with a letter without our hotkey, exit pronto. if (e.Key != Key.CapsLock && !e.ModifierCapsLock) { return(false); } // Check if the letter has changed whilst the popup is showing, in this case we'll show another selector window. if (_sActiveSelectorWindow != null && _IsLetterPress(e.Key)) { if (_sActiveSelectorWindow.Key != e.Key) { var left = _sActiveSelectorWindow.Left; var top = _sActiveSelectorWindow.Top; _HidePopup(); if (!LetterMappings.KeyToWindowMap.TryGetValue(e.Key, out _sActiveSelectorWindow)) { return(false); } _ShowPopup(e.ModifierAnyShift, (Int32)left, (Int32)top); return(true); } } if (_sActiveSelectorWindow == null) { if (e.Key == Key.CapsLock) { return(true); // disable capslock } if (!LetterMappings.KeyToWindowMap.TryGetValue(e.Key, out _sActiveSelectorWindow)) { return(false); } } if (_sActiveSelectorWindow == null) { return(false); } var selectorShowing = _sActiveSelectorWindow.IsVisible; // Change case if shift is used. // First we need an additional shift key press to activate it, then we'll handle it as a modifier. if (e.Key == Key.LeftShift || e.Key == Key.RightShift) { if (selectorShowing) { if (isDown) { _sActiveSelectorWindow.ToUpper(); _sShiftTimer.Restart(); } else { _sActiveSelectorWindow.ToLower(); } } return(selectorShowing); } else if (selectorShowing && !e.ModifierAnyShift) { _sActiveSelectorWindow.ToLower(); } // If the selector is showing, and this is a down press, go to next key. if (selectorShowing && isDown && e.Key != Key.CapsLock) { if (e.Key == Key.Left || (e.Key != Key.Right && e.ModifierAnyAlt)) { _sActiveSelectorWindow.SelectPrevious(); } else { _sActiveSelectorWindow.SelectNext(); } return(true); } // If selector is not showing and this is a down press, show it. if (!selectorShowing && isDown && e.Key != Key.CapsLock) { _sActiveKeyboardWindow = GetForegroundWindow(); var position = Caret.GetPosition(_sActiveKeyboardWindow); // Get the monitor on which we are and see if we need to adjust location to avoid falling off. IntPtr monitor; if (position.X == 0 && position.Y == 0) { monitor = MonitorFromWindow(_sActiveKeyboardWindow, MONITOR_DEFAULTTONEAREST); } else { monitor = MonitorFromPoint(position, MONITOR_DEFAULTTONEAREST); } // As far as I understand, WPF's coordinates are relative to the default monitor. // This means that once we've got raw pixels, we need to scale these relative to the DPI of the default // monitor in order to get WPF coordinates. // In the below we'll do everything in raw pixels prior to scaling to WPF coordinates. // The position is measured in dpi of the default monitor. IntPtr defaultMonitor = MonitorFromPoint(default(Point), MONITOR_DEFAULTTOPRIMARY); UInt32 mainDpiX, mainDpiY; Double mainScale = 1.0; if (GetDpiForMonitor(defaultMonitor, DpiType.EFFECTIVE, out mainDpiX, out mainDpiY) == IntPtr.Zero) { // Scale positioning. mainScale = mainDpiX / 96.0; } if (monitor != IntPtr.Zero) { // Get monitor size. MONITORINFO info = new MONITORINFO(); info.cbSize = Marshal.SizeOf(info); if (GetMonitorInfo(monitor, ref info)) { // Convert width and height from WPF coordinates to raw pixels using the scale of the default monitor. var width = _sActiveSelectorWindow.ActualWidth * mainScale; var height = _sActiveSelectorWindow.ActualHeight * mainScale; if (position.X == 0 && position.Y == 0) { // This can happen in two cases: // - there is no caret // - the application does not use win32 caret api // Slap the popup in the middle of the screen as workaround for now. position.X = info.rcWork.Left + (info.rcWork.Right - info.rcWork.Left) / 2 - (Int32)(width / 2.0); position.Y = info.rcWork.Top + (info.rcWork.Bottom - info.rcWork.Top) / 2 - (Int32)(height / 2.0); } // Adjust position to fit within the given dimensions. if (position.X + width > info.rcWork.Right) { position.X = info.rcWork.Right - (Int32)width; } if (position.Y + height > info.rcWork.Bottom) { position.Y = info.rcWork.Bottom - (Int32)height; } } } // If we got nowhere reasonable to put the popup, mark as handled and don't show it. if (position.X == 0 && position.Y == 0) { return(true); } // Finally, convert the position from raw pixels to WPF coordinates. if (mainScale > 1.0) { position.X = (Int32)Math.Round(position.X / mainScale); position.Y = (Int32)Math.Round(position.Y / mainScale); } _ShowPopup(e.ModifierAnyShift, position.X, position.Y); return(true); } // If the selector is showing, mark key as handled. if (selectorShowing && (e.Key != Key.CapsLock || isDown)) { return(true); } // If the selector is not showing, disable the activator key. if (!selectorShowing) { return(true); } // Using the shift key is difficult because the user has to release many keys at once. // Often the shift key is release slightly before the rest which causes the letters to be // lower cased just before selecting. We allow a little time interval in which we ignore the shift up. if (_sShiftTimer.ElapsedMilliseconds < _kReleaseErrorMargin) { _sActiveSelectorWindow.ToUpper(); } // As with the shift key, if the alt key is pressed when the key is sent this really // sends a alt+key shortcut combination to the target application. We allow for an error // margin and delay the sending of input if alt is used (but not enough to be really noticable). // Todo: instead send an alt up? _SendSelectedLetterAsKeyPress(delayInput: e.ModifierAnyAlt); return(true); }