Exemple #1
0
        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));
            }
        }
Exemple #2
0
        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);
        }