How to hook Win + Tab using LowLevelKeyboardHook - c#

In a few words: blocking Win up after Win + Tab makes Windows think Win is still down, so then pressing S with the Win key up for example will open the search charm rather than just type "s"... until the user presses Win again. Not blocking it means the Windows Start menu will show up. I'm in a conundrum!
I have no trouble hooking into shortcuts using Alt + Tab using LowLevelKeyboardHook, or Win + Some Ubounded Key using RegisterHotKey. The problem happens only with the Win key using LowLevelKeyboardHook.
In the example below, I'm taking over the Win up event when the Win + Tab combination is detected. This results in making every following keystrokes behave as if the Win key was still down.
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode != HC_ACTION)
return CallNextHookEx(_hookID, nCode, wParam, lParam);
var keyInfo = (Kbdllhookstruct)Marshal.PtrToStructure(lParam, typeof(Kbdllhookstruct));
if (keyInfo.VkCode == VK_LWIN)
{
if (wParam == (IntPtr)WM_KEYDOWN) {
_isWinDown = true;
} else {
_isWinDown = false;
if (_isWinTabDetected) {
_isWinTabDetected = false;
return (IntPtr)1;
}
}
}
else if (keyInfo.VkCode == VK_TAB && _isWinDown) {
_isWinTabDetected = true;
if (wParam == (IntPtr)WM_KEYDOWN) {
return (IntPtr)1;
} else {
_isWinTabDetected = true;
Console.WriteLine("WIN + TAB Pressed");
return (IntPtr)1;
}
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
}
}
You can find the complete code here (note that it should replace your Program.cs in an empty WinForms project to run): https://gist.github.com/christianrondeau/bdd03a3dc32a7a718d62 - press Win + Tab and the Form title should update each time the shortcut is pressed.
Note that the intent of hooking into this specific combination is to provide an Alt + Tab alternative without replacing Alt + Tab itself. An answer providing the ability to launching custom code using Win + Tab will also be accepted.
Here are my ideas, for which I could not find documentation. All would potentially answer my question successfully.
Tell Windows to "cancel" the Win up without actually triggering it
Prevent Windows from launching the Start menu once
Hook directly in the Windows' Win + event rather than manually hooking into the keystrokes (This would be by far my first choice if that exists)

System need to know you release the Windows key.
I check the difference between my own hook who doesn't have this problem and the only diffence between your and mine is this line :
if (_isWinTabDetected) {
_isWinTabDetected = false;
return (IntPtr)1; //THIS LINE
}

This appears to do exactly what you want (omit RWin if you wish).
Please be considerate and unregister this KB hook when your app loses focus!
[DllImport("user32.dll")]
static extern short GetAsyncKeyState(System.Windows.Forms.Keys vKey);
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode == HC_ACTION)
{
var keyInfo = (Kbdllhookstruct) Marshal.PtrToStructure(lParam, typeof (Kbdllhookstruct));
if ((int) wParam == WM_KEYDOWN
&& keyInfo.VkCode == VK_TAB
&& (GetAsyncKeyState(Keys.LWin) < 0 || GetAsyncKeyState(Keys.RWin) < 0))
{
_mainForm.Text = "Win + Tab was pressed " + (++_winTabPressCounter) + " times";
return (IntPtr) 1;
}
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
I tried several things before discovering this technique.
This post was the most helpful https://stackoverflow.com/a/317550/55721

Related

In Windows 10, is it possible to create a service/thread that listens if any textbox has been focused to open TabTip keyboard?

In Windows 10 Tablet mode, the TabTip keyboard is shown whenever any textbox (regardless of type, be it from a Java application, a Metro app, or a WPF app) is focused.
It seems impossible to implement this as a service using only simple Window hooks since Metro apps do not return a Window element, which led to the hypothesis that there must be a system call happening whenever any textfield is focused, when in Tablet mode.
I've been researching for days, but to no avail. The most useful thing I have seen would be this thread where the responder reverse-engineered the behavior when tapping the Keyboard key in the taskbar in Tablet mode.
I also tried this block of code, but this seems to only catch a small portion of the text fields:
public void DetectKeyboard()
{
AttemptKeyboardDetection:
try
{
AttachThreadInput(GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero), GetCurrentThreadId(), true);
IntPtr focus = GetFocus();
AttachThreadInput(GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero), GetCurrentThreadId(), false);
if (GetClassName(focus, this.StringBuilder, this.StringBuilder.Capacity))
{
ActiveProcessFocus = this.StringBuilder.ToString();
Debug.WriteLine("Active Process: " + ActiveProcessFocus);
if ((PreviousProcessFocus != ActiveProcessFocus) && !ActiveProcessFocus.Contains("KeyboardOpener"))
{
Debug.WriteLine("Checking if editing...");
if ((((ActiveProcessFocus == "Edit") || (ActiveProcessFocus == "SearchPane")) || (ActiveProcessFocus.Contains("RichEdit") || ActiveProcessFocus.Contains("SearchEdit"))) || ((ActiveProcessFocus.Contains("TextfieldEdit") || ActiveProcessFocus.Contains("Afx:00400000")) || (((ActiveProcessFocus == "_WwG") || (ActiveProcessFocus == "Scintilla")) || (ActiveProcessFocus == "SPEAD0C4"))))
{
Debug.WriteLine("Editing! Opening keyboard...");
try
{
ProcessStartInfo startInfo = new ProcessStartInfo(#"C:\Program Files\Common Files\Microsoft Shared\ink\TabTip.exe");
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
Process.Start(startInfo);
}
catch
{
}
}
else
{
try
{
SendMessage(FindWindow("IPTip_Main_Window", null), 0x112, (IntPtr) 0xf060, IntPtr.Zero);
foreach (Process process in Process.GetProcessesByName("osk"))
{
process.Kill();
}
}
catch
{
}
}
PreviousProcessFocus = ActiveProcessFocus;
}
}
Thread.Sleep(250);
goto AttemptKeyboardDetection;
}
catch
{
goto AttemptKeyboardDetection;
}
}
[DllImport("user32.dll")]
private static extern IntPtr AttachThreadInput(IntPtr AttachId, IntPtr AttachToId, bool AttachStatus);
[DllImport("user32.dll")]
private static extern bool GetClassName(IntPtr hWnd, StringBuilder ClassName, int ClassMax);
[DllImport("kernel32.dll")]
private static extern IntPtr GetCurrentThreadId();
[DllImport("user32.dll")]
private static extern IntPtr GetFocus();
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
private static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, IntPtr ThreadId);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
since not all text field elements contain "Edit", etc. in their element names.
Since I'm sure I've exhausted what I can find with my limited knowledge in Windows programming and through research, does anyone know of a system call that can be listened to that is similar to the one called when in Tablet mode? Or is this really impossible without diving in to assembly or even making viruses?

Mouse hook getting disconnected

I'm trying to implement a color picker that takes the color from a pixel everywhere in the screen.
To do that I'm planning to use a global mouse hook to listen to WM_MOUSEMOVE in order to update the color as the mouse is moved around and listen to mouse clicks to confirm (WM_LBUTTONDOWN) or cancel(WM_RBUTTONDOWN) the operation.
I have followed one of the many tutorials around and I came up with this (in a Console Application, just to test out if the process works):
static IntPtr hook;
static bool click;
static NativeMethods.LowLevelHookStruct llhs;
static void Main(string[] args)
{
hook = NativeMethods.SetWindowsHookEx(NativeMethods.WH_MOUSE_LL, MouseHookCallback, (IntPtr)null, 0);
if (hook != IntPtr.Zero)
{
Console.WriteLine("Hook Set");
while (!Console.KeyAvailable) {
Console.WriteLine("{0} {1} {2}", hook, llhs.pt.x, llhs.pt.y);
if(click) Console.WriteLine("click!");
click = false;
System.Threading.Thread.Sleep(250);
}
}
}
and
public static IntPtr MouseHookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
NativeMethods.LowLevelHookStruct hookStruct = (NativeMethods.LowLevelHookStruct)Marshal.PtrToStructure(lParam, typeof(NativeMethods.LowLevelHookStruct));
if (NativeMethods.MouseMessages.WM_MOUSEMOVE == (NativeMethods.MouseMessages)wParam)
{
llhs = hookStruct;
}
if (NativeMethods.MouseMessages.WM_LBUTTONDOWN == (NativeMethods.MouseMessages)wParam)
{
click = true;
}
else if (NativeMethods.MouseMessages.WM_RBUTTONDOWN == (NativeMethods.MouseMessages)wParam)
{
}
}
return NativeMethods.CallNextHookEx(hook, nCode, wParam, lParam);
}
NativeMethods is simply a class where I keep all the DllImport related stuff.
Once I run the console application, the mouse cursor gets stuck for a couple of seconds, and in the console I get this - even while the cursor is stuck
Hook Set
3945554872 0 0
3945554872 0 0
3945554872 0 0
3945554872 0 0
...
Going at it in debug, it seems that my hook is never called, not even once.
Any idea what might be wrong?
Following #Hans Passant comment I moved my test code to a WinForms application, and callbacks started coming.
Then it was just a matter to find out that the callback was being garbage collected, so all I had to do was to change
hook = NativeMethods.SetWindowsHookEx(NativeMethods.WH_MOUSE_LL, MouseHookCallback, (IntPtr)null, 0);
to
private NativeMethods.LowLevelHookProc _hookCallback;
...
_hookCallback = new NativeMethods.LowLevelHookProc(MouseHookCallback);
hook = NativeMethods.SetWindowsHookEx(NativeMethods.WH_MOUSE_LL, _hookCallback, (IntPtr)null, 0);
in order to keep a reference to the callback so that it would not be GCed.

Global hotkey to set textbox value

Currently I have global hot keys setup to paste the contents of a text box into the current active form (browser). These work perfectly fine (CTRL + 0 - 9), however I'm trying to do a reverse of that by setting the text box value from an external value. Initially I thought it would be easy, since they're global hotkeys I'd just highlight what I want and then press the hotkey (ALT + 0 - 9) and it would SendKeys.Send("^c"), and then set the TextBox.Text = Clipboard.GetText(), however this doesn't behave as expected and returns seemingly random values from in my code/past clipboard entries.
I had a look around and found various things which also didn't help. It's important to note that I'll always be calling this global hotkey from the current active window, so I don't need to find/set the active window, meerly just send a ctrl + C command and have it reliably execute it.
Any help is appreciated, thanks.
RegisterHotKey(Handle, 12, 0x0001, (int) Keys.D1);
case (12):
if (x.Text == "" || x.Text == #"YYYYYY")
{
//Window is in Focus and the text is already highlighted
SendKeys.SendWait("^c");
x.Text = Clipboard.GetText();
}
return;
Bonus points, can I expand the clipboard class to return the count of the selection before doing anything?
Edit:
My attempt at Sending WM_COPY:
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
case (12):
if (x.Text == "" || x.Text == #"YYYYYY")
{
//Window is in Focus and the text is already highlighted
SendMessage(m.HWnd, 0x0301, (IntPtr)0, (IntPtr)0);
x.Text = Clipboard.GetText();
}
return;

How do I override Ctrl+Shift+0 (zero) for WinForms RichTextBox?

For my subclassed RichTextBox class, I can capture, suppress the default behaviour of and re-purpose Ctrl+Shift+#, as long as # is between 1 and 9. For Ctrl+Shift+0, I can't. I have experimented with ProcessCmdKey in the form class and onKeyDown and PreProcessMessage in the control class.
Here is sample code meant for the control class that should suppress Ctrl+Shift+0 but does not:
public override bool PreProcessMessage(ref Message msg)
{
bool cancel = false;
int vKeyCode = (int)msg.WParam;
if(msg.Msg == WinApi.WM_KEYDOWN)
{
bool ctrlDown = (WinApi.GetKeyState(Keys.ControlKey) & (1 << 16)) == (1 << 16);
bool altDown = (WinApi.GetKeyState(Keys.Alt) & (1 << 16)) == (1 << 16);
bool shiftDown = (WinApi.GetKeyState(Keys.ShiftKey) & (1 << 16)) == (1 << 16);
if(ctrlDown && shiftDown && vKeyCode == (int)Keys.D0)
{
Debug.WriteLine("Cancel!");
cancel = true;
}
}
return cancel ? true : base.PreProcessMessage(ref msg);
}
However, changing Keys.D0 to Keys.D1 shows that the sample otherwise works.
If it's a clue, the default behaviour of the RichTextBox, in response to Ctrl+Shift+0, is to change the font. I went hunting for documentation that mentions this as a built-in shortcut but I didn't find anything (maybe I'm not using the correct search terms).
How should I detect Ctrl+Shift+0 so that I can suppress the default behaviour and write my own?
After some tries, I've found out how to actually suppress the Ctrl + Shift + D0. However the new problem was even tougher, the Ctrl + Shift + D0 was suppressed OK but the beep sound was generated when releasing the Ctrl and Shift, that's so annoying because you said you wanted to override these keys combination not discard it. So the beep sound should not be generated.
After searching much with a hope there was some style to apply to RichTextBox to prevent the beep sound or some message to discard resulting in suppressing the beep sound but there are not any such things. I were almost disappointed and intended to let your question unanswered forever. I didn't want to add the answer which just partially solved your problem. However it was luckily that I tried sending some key instead of the discarded 0 key to consume the keys combination and make the keys combination valid so no beep sound would be generated. Here is the entire code for you, note that we have to use some global low-level keyboard hook here, as I said the Application-level message filter also couldn't help:
[DllImport("user32")]
private static extern IntPtr SetWindowsHookEx(int hookType, KeyboardLowLevelProc proc, IntPtr moduleHandle, int threadID);
[DllImport("user32")]
private static extern int UnhookWindowsHookEx(IntPtr hHook);
[DllImport("user32")]
private static extern IntPtr CallNextHookEx(IntPtr hHook, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32")]
private static extern IntPtr GetModuleHandle(string moduleName);
public struct KBDLLHOOKSTRUCT
{
public Keys key;
public int scanCode;
public int flags;
public int time;
public IntPtr extra;
}
public delegate IntPtr KeyboardLowLevelProc(int hCode, IntPtr wParam, IntPtr lParam);
public IntPtr KeyboardLowLevelCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0) {
KBDLLHOOKSTRUCT kbd = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
if (kbd.key == Keys.D0 && blockD0) {
if(ModifierKeys == (Keys.Control | Keys.Shift)) {
SendKeys.Send("{ESC}");
//Add custom code as the response to Ctrl + Shift + D0 here
//....
}
return new IntPtr(1);//Discard the default behavior
}
}
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
bool blockD0;
KeyboardLowLevelProc proc; //this should be declared in the form scope
IntPtr hHook;
//your Form1 constructor
public Form1(){
InitializeComponent();
//Get current module Handle
IntPtr currentModuleHandle = GetModuleHandle(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName);
//Set the keyboard hook
hHook = SetWindowsHookEx(13, proc, currentModuleHandle, 0);//WH_KEYBOARD_LL = 13
//register these Key events for your richTextBox1
richTextBox1.KeyDown += (s, e) => {
if(e.KeyCode != Keys.D0) blockD0 = true;
};
richTextBox1.KeyUp += (s, e) => {
if (ModifierKeys == Keys.None) blockD0 = false;
};
//Unhook keyboard when form is closed
FormClosed += (s,e) => {
if (hHook != IntPtr.Zero) {
UnhookWindowsHookEx(hHook);
hHook = IntPtr.Zero;
}
}
}
A little about the explanation: I do not understand exactly why we have to use Global Low-level Keyboard hook here, I guess that when the keys combination Ctrl + Shift + D0 is pressed, there may be some key message cloned and dispatched to another thread, that's why all the manual interceptions in the current thread can't intercept or override the Ctrl + Shift + D0, however the global low-level keyboard hook can handle the key messages in all threads of the current module and it can intercept any key messages.
I mentioned about the beep sound problem, if you want to experience it, just remove the SendKeys.Send("{ESC}");, in fact you can try some other keys like 1, 2, ... they also make the keys combination Ctrl + Shift + ... valid and help avoid any beep sound.
UPDATE
The solution above works OK, it's the best solution with the point that the Ctrl + Shift + D0 should be discarded cleanly and totally. However it's a little long (as you can see). I've found that when you press the Ctrl + Shift + D0, the message WM_INPUTLANGCHANGEREQUEST is sent, this message causes the behavior which you don't want. So we can try another solution, with PreProcessMessage you can still catch the combination Ctrl + Shift + D0 but you just can't discard it (because it's dispatched to another thread), that means you can add your own code there, instead of discarding the Ctrl + Shift + D0, we can discard the effect/behavior it causes instead by discarding the message WM_INPUTLANGCHANGEREQUEST. We have the following code:
//Create a custom RichTextBox class
public class CustomRichTextBox : RichTextBox {
protected override void WndProc(ref Message m){
if(m.Msg == 0x50) return; //WM_INPUTLANGCHANGEREQUEST = 0x50
base.WndProc(ref m);
}
public override bool PreProcessMessage(ref Message msg) {
if (msg.Msg == 0x100)//WM_KEYDOWN = 0x100
{
Keys keyData = (Keys)msg.WParam | ModifierKeys;
if(keyData == (Keys.Control | Keys.Shift | Keys.D0)){
//your own code goes here...
}
}
return base.PreProcessMessage(ref msg);
}
}
You can see that the second approach is much shorter, however as I said, it doesn't actually suppress the Ctrl + Shift + D0, it just suppresses the default behavior caused by the message WM_INPUTLANGCHANGEREQUEST. I guess it's enough to solve your problem.

Get handle to desktop / shell window

In one of my programs I need to test if the user is currently focusing the desktop/shell window. Currently I'm using GetShellWindow() from user32.dll and compare the result to GetForegroundWindow().
This approach is working until someone changes the desktop wallpaper, but as soon as the wallpaper is changed the handle from GetShellWindow() doesn't match the one from GetForegroundWindow() anymore and I don't quite get why that is. (OS: Windows 7 32bit)
Is there a better approach to check if the desktop is focused? Preferably one that won't be broken if the user changes the wallpaper?
EDIT: I designed a workaround: I'm testing the handle to have a child of class "SHELLDLL_DefView". If it has, the desktop is on focus. Whilst it's working at my PC that doesn't mean it will work all the timeā€¦
The thing changed a little bit since there are slideshows as wallpaper available in Windows 7.
You are right with WorkerW, but this works only with wallpaper is set to slideshow effect.
When there is set the wallpaper mode to slideshow, you have to search for a window of class WorkerW and check the children, whether there is a SHELLDLL_DefView.
If there is no slideshow, you can use the good old GetShellWindow().
I had the same problem some months ago and I wrote a function for getting the right window. Unfortunately I can't find it. But the following should work. Only the Win32 Imports are missing:
public enum DesktopWindow
{
ProgMan,
SHELLDLL_DefViewParent,
SHELLDLL_DefView,
SysListView32
}
public static IntPtr GetDesktopWindow(DesktopWindow desktopWindow)
{
IntPtr _ProgMan = GetShellWindow();
IntPtr _SHELLDLL_DefViewParent = _ProgMan;
IntPtr _SHELLDLL_DefView = FindWindowEx(_ProgMan, IntPtr.Zero, "SHELLDLL_DefView", null);
IntPtr _SysListView32 = FindWindowEx(_SHELLDLL_DefView, IntPtr.Zero, "SysListView32", "FolderView");
if (_SHELLDLL_DefView == IntPtr.Zero)
{
EnumWindows((hwnd, lParam) =>
{
if (GetClassName(hwnd) == "WorkerW")
{
IntPtr child = FindWindowEx(hwnd, IntPtr.Zero, "SHELLDLL_DefView", null);
if (child != IntPtr.Zero)
{
_SHELLDLL_DefViewParent = hwnd;
_SHELLDLL_DefView = child;
_SysListView32 = FindWindowEx(child, IntPtr.Zero, "SysListView32", "FolderView"); ;
return false;
}
}
return true;
}, IntPtr.Zero);
}
switch (desktopWindow)
{
case DesktopWindow.ProgMan:
return _ProgMan;
case DesktopWindow.SHELLDLL_DefViewParent:
return _SHELLDLL_DefViewParent;
case DesktopWindow.SHELLDLL_DefView:
return _SHELLDLL_DefView;
case DesktopWindow.SysListView32:
return _SysListView32;
default:
return IntPtr.Zero;
}
}
In your case you would call GetDesktopWindow(DesktopWindow.SHELLDLL_DefViewParent); to get the top-level window for checking whether it is the foreground window.
Here is a workaround that uses GetClassName() to detect if the desktop is active:
When Windows first starts, the desktop's Class is "Progman"
After changing the wallpaper, the desktop's Class will be "WorkerW"
You can test against these to see if the desktop is focused.
[DllImport("user32.dll")]
static extern int GetForegroundWindow();
[DllImport("user32.dll")]
static extern int GetClassName(int hWnd, StringBuilder lpClassName, int nMaxCount);
public void GetActiveWindow() {
const int maxChars = 256;
int handle = 0;
StringBuilder className = new StringBuilder(maxChars);
handle = GetForegroundWindow();
if (GetClassName(handle, className, maxChars) > 0) {
string cName = className.ToString();
if (cName == "Progman" || cName == "WorkerW") {
// desktop is active
} else {
// desktop is not active
}
}
}

Categories