10

I am working on a WPF/C# application for completing forms. I am trying to find a way to determine if the TapTip keyboard (TabTip.exe / metro-like keyboard for windows 8 desktop) is minimized / not visible in windows 8.

I have been able to detect if the osk keyboard (osk.exe / windows accessibility on-screen keyboard) is minimized, but the same process does not seem to work with the TabTip keyboard.

To detect if the keyboard is minimized I:
1. Find the keyboard's process
2. Get the MainWindowHandle
3. Use the showCmd property of the WINDOWPLACEMENT (found using MainWindowHandle)
4. Use showCmd value to determine if window is minimized

The problems I have run into are:
- the TabTip process has a MainWindowHandle of 0 (so I can't use it to find the WINDOWPLACEMENT information)
- the values for WINDOWPLACEMENT.showCmd are the same when TabTip is open and minimized

In order to find the handle of the TabTip window I used ENUMWINDOWS to get all the window handles, GETWINDOWTHREADPROCESSID to get the process ids, then compared the ids to the TabTip process id.

Any help with this would be appreciated. Also this is my first post. I think I did this right, but if not please let me know how to fix it.

user
  • 101
  • 1
  • 1
  • 3

3 Answers3

12

I tried a few different methods before finding one which works. Using IsWindowVisible() didn't work and I also didn't have any joy with GetWindowPlacement() or GetIconic(). In the end I used GetWindowLong() and checked for the WS_VISIBLE coming back. A quick console app to demonstrate is as follows:

using System;
using System.Diagnostics;
using Microsoft.Win32;
using System.Runtime.InteropServices;
using System.Threading;

namespace CSharpTesting
{
    class Program
    {
        /// <summary>
        /// The window is initially visible. See http://msdn.microsoft.com/en-gb/library/windows/desktop/ms632600(v=vs.85).aspx.
        /// </summary>
        public const UInt32 WS_VISIBLE  = 0X94000000;
        /// <summary>
        /// Specifies we wish to retrieve window styles.
        /// </summary>
        public const int GWL_STYLE = -16;

        [DllImport("user32.dll")]
        public static extern IntPtr FindWindow(String sClassName, String sAppName);

        [DllImport("user32.dll", SetLastError = true)]
        static extern UInt32 GetWindowLong(IntPtr hWnd, int nIndex);

        static void Main(string[] args)
        {
            // Crappy loop to poll window state.
            while (true)
            {
                if (IsKeyboardVisible())
                {
                    Console.WriteLine("keyboard is visible");
                }
                else
                {
                    Console.WriteLine("keyboard is NOT visible");
                }

                Thread.Sleep(1000);
            }
        }

        /// <summary>
        /// Gets the window handler for the virtual keyboard.
        /// </summary>
        /// <returns>The handle.</returns>
        public static IntPtr GetKeyboardWindowHandle()
        {
            return FindWindow("IPTip_Main_Window", null);
        }

        /// <summary>
        /// Checks to see if the virtual keyboard is visible.
        /// </summary>
        /// <returns>True if visible.</returns>
        public static bool IsKeyboardVisible()
        {
            IntPtr keyboardHandle = GetKeyboardWindowHandle();

            bool visible = false;

            if (keyboardHandle != IntPtr.Zero)
            {
                UInt32 style = GetWindowLong(keyboardHandle, GWL_STYLE);
                visible = (style == WS_VISIBLE);
            }

            return visible;
        }
    }
}
Barrie
  • 1,444
  • 19
  • 26
  • This doesn't seem to work if the on-screen keyboard is on a different screen than where the cursor is. I'm on a Windows 10 machine. – Matt Becker Nov 11 '15 at 14:26
  • 1
    It doesn't work after user first logs in to windows. It reports that keyboard is visible when it's not. However, if user opens keyboard manually then closes it, it starts reporting correctly. Anyone have a fix for this? – mikesl Sep 24 '16 at 17:36
  • For me this is what works all the time right after login and at other times: UInt32 WS_VISIBLE = 0X94000000; ... visible = (style == WS_VISIBLE); – mikesl Sep 24 '16 at 18:02
  • 3
    style is a long type bit pattern used to hold N amount of style setting flags, so if you want to know if the WS_VISIBLE flag is set you use the & bit operator: "style & WS_VISIBLE" – stuicidle May 18 '18 at 14:07
  • in your message you say you check for `WS_DISABLED` but in your code you actually check for `WS_VISIBLE` – Couitchy Jun 28 '18 at 09:57
  • @stuicidle you're right but in this case `WS_VISIBLE` needs to be the *only* activated flag. I just tested it. So his code is OK. – Couitchy Jun 28 '18 at 10:15
  • The code sample initially used `WS_DISABLED` but @mikesl had made an edit to `WS_VISIBLE`. I have updated the opening text to reflect this and removed the definition for `WS_DISABLED` as it was no longer necessary. Thanks for the heads-up. – Barrie Jun 29 '18 at 10:24
2

This totally works!

//
// BOOL IsVirtualKeyboardVisible()
//
// Returns TRUE if Virtual Keyboard/Input Pane is visible
// Returns FALSE if Virtual Keyboard/Input Pane is not visible

__declspec(dllexport) BOOL __cdecl IsVirtualKeyboardVisible()
{
    BOOL    bRet = FALSE;
    RECT    InputPaneScreenLocation = { 0, 0, 0, 0 };

    __try
    {
        HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

        IFrameworkInputPane *IinputPane = NULL;

        if (SUCCEEDED(hr))
        {
            //
            // http://msdn.microsoft.com/en-us/library/windows/desktop/hh706967(v=vs.85).aspx
            //
            hr = CoCreateInstance(__uuidof(FrameworkInputPane), 0, CLSCTX_ALL, __uuidof(IFrameworkInputPane), (LPVOID*)&IinputPane);
            IinputPane->Location(&InputPaneScreenLocation);

            if (InputPaneScreenLocation.bottom == 0 && InputPaneScreenLocation.left == 0 &&
                InputPaneScreenLocation.right == 0 && InputPaneScreenLocation.top == 0)
            {
                // VKB is not visible
                bRet = FALSE;
            }
            else
            {
                // VKB is visible
                bRet = TRUE;
            } 
        }

    }   // try
    __finally
    {
        CoUninitialize();
    }

    return bRet;
}
  • 3
    ... and why does this work? Please explain why this code works as this discourages mere copying and pasting without complete understanding. The OP should be able to understand the ramifications behind your choices in designing this code and determine whether or not this code is suitable for solving the task. – rayryeng Jun 30 '14 at 22:27
  • This solution works consistently for me at v1909. It uses the IFrameworkInputPane interface, linked in the code, which specifically locates the input pane used by touch keyboards and IMEs. – Greg Jan 24 '20 at 04:17
1

If I remember correctly, the window class name for TabTip.exe is IPTip_Main_Window. You can use the Win32 API FindWindow to get the HWND of TabTip.exe. This is more reliable than using the window title and recommended as some windows can have empty titles (or the title can change).

Your current approach using EnumWindows could be flawed due to a single process having many windows (or windows with child windows). You can use a tool like Spy++ to find the actual window you want and the respective class name.

You can still use GetWindowHandleThreadProcessId to retrieve the processID at that point, though I do not think you will need it for simple window state monitoring.

Also, try using Win32 APIs instead of whatever is built into the CLR. For example GetWindowPlacement.

Note from MSDN:

The flags member of WINDOWPLACEMENT retrieved by this function is always zero. If the window identified by the hWnd parameter is maximized, the showCmd member is SW_SHOWMAXIMIZED. If the window is minimized, showCmd is SW_SHOWMINIMIZED. Otherwise, it is SW_SHOWNORMAL.

Hope that helps, if you still need further assistance leave a comment and I'll make an edit once I get back to my Win8 machine.

Erik
  • 12,730
  • 5
  • 36
  • 42
  • Thanks for the quick response. I switched to using FindWindow like you suggested. I then used Spy++ to verify that the correct window was being found. Unfortunately when using GetWindowPlacement, showCmd only had a value of 0 (SW_HIDE) when the keyboard's process was killed. The rest of the time showCmd had a value of 1 (SW_NORMAL) regardless of whether or not it was visible. I have also tried using GetWindowRect to see if the window was being "hidden" off screen. When not visible the coordinates returned were the same as it's last visible position on the screen. – user Jan 02 '14 at 22:57
  • Did you ever figure this out? I've tried both methods and neither seem to work. I think TabTip.exe is a special process which is coupled with a service so the standard methods of detecting whether a hwnd is minimized/closed don't seem to be working. – YasharBahman Feb 28 '14 at 17:34
  • I never did quite get a chance to look into it further, sorry! – Erik Mar 14 '14 at 19:51
  • Well, I can confirm that you can use FindWindow to figure out if TabTip is already running or not. But if it's running and hidden, I just cannot figure out how to tell it to show the window again. I've tried using ShowWindow with SW_SHOW, but no luck there... There's this ugly workaround of sending SC_CLOSE to that window and launching TabTip again. Not nice. – Aros Dec 02 '20 at 16:47