I'm having a problem with a Global Keyboard Hook.
For the most part, it works. Bu,t in the section below, it should be stopping the enter key from being passed onto the focused program. It only works about half the time.
Any ideas as to why it would block the enter key sometimes and not others?
Here is the relevant code:
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook,
LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private const int WM_KEYUP = 0x0101;
private static LowLevelKeyboardProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;
And:
_hookID = SetHook(_proc);
And:
private static IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
GetModuleHandle(curModule.ModuleName), 0);
}
}
private delegate IntPtr LowLevelKeyboardProc(
int nCode, IntPtr wParam, IntPtr lParam);
private static IntPtr HookCallback(
int nCode, IntPtr wParam, IntPtr lParam)
{
Keys keyName;
bool validKey;
int vkCode = Marshal.ReadInt32(lParam);
keyName = (Keys)vkCode;
validKey = monitorKeys.Contains(keyName.ToString()); //checks if the current key is in our list of keys to monitor
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
{
vkCode = Marshal.ReadInt32(lParam);
keyName = (Keys)vkCode;
if (validKey && keyName == Keys.Enter && altActive == false && ctrlActive == false)
{
char c = new char();
c = KeyConvertor.ToAscii(keyName);
}
displayBuffer += c.ToString();
//do some db lookups on the current word here
lblBuffer.Text = displayBuffer;
return (IntPtr)1; //no key is sent to program This only works about half the time even though (IntPtr)1 is being returned.
}
return CallNextHookEx(_hookID, nCode, wParam, lParam); //key is passed on to program
}
}
else if (nCode >= 0 && wParam == (IntPtr)WM_KEYUP)
{
//trap the key
return (IntPtr)1;
}
return CallNextHookEx(_hookID, nCode, wParam, lParam); //key is passed on to program
}
Related
I want to get all text of a window.
I prepared below code.But i can only get window captions/titles.
How can i get all text written inside a window ?
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
Process[] processlist = Process.GetProcesses();
foreach (Process process in processlist)
{
if (!String.IsNullOrEmpty(process.MainWindowTitle))
{
IntPtr xero = new IntPtr(0);
var x = FindWindowByCaption(xero, process.MainWindowTitle);
int length = GetWindowTextLength(x);
StringBuilder sb = new StringBuilder(length + 1);
GetWindowText(x, sb, sb.Capacity);
}
}
You need to enumerate all child windows of the top level windows. You can use EnumChildWindows API in order to accomplish that.
Here is the sample code i have written in C# for you
internal class Program
{
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool EnumChildWindows(IntPtr hWndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, StringBuilder lParam);
const uint WM_GETTEXT = 0x000D;
static bool EnumAllChilds(IntPtr hWnd, IntPtr lParam)
{
StringBuilder sb = new StringBuilder(2048);
SendMessage(hWnd, WM_GETTEXT, new IntPtr(sb.Capacity), sb);
if (!string.IsNullOrEmpty($"{sb}"))
{
Console.WriteLine($"\t{hWnd:X}\t{sb}");
}
EnumChildWindows(hWnd, EnumAllChilds, lParam);
return true;
}
static bool EnumTopLevel(IntPtr hWnd, IntPtr lParam)
{
StringBuilder sb = new StringBuilder(2048);
SendMessage(hWnd, WM_GETTEXT, new IntPtr(sb.Capacity), sb);
Console.WriteLine($"TopLevel: hWnd: {hWnd:X}\t{(string.IsNullOrEmpty($"{sb}") ? "No Caption" : $"{sb}")}");
// Call for child windows
EnumChildWindows(hWnd, EnumAllChilds, lParam);
return true;
}
static void Main(string[] args)
{
// Call for TopLevel windows
EnumWindows(EnumTopLevel, IntPtr.Zero);
Console.ReadLine();
}
}
I made before a program where I could open a process of the notepad and while it's opened, be able to write in it from the C# program console. Now I'm trying to do the same but with the excel, I can run the process, I can open it and I can kill it. But when I try to write in it with the SendMessage() method, nothing happens, is there a way I can do this? Or am I missing something? Thanks!
Here's what I tried so far
Declarations
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool PostMessage(HandleRef hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
//include SendMessage
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, string lParam);
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, int lParam);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, string windowTitle);
[DllImport("User32.dll")]
static extern int SetForegroundWindow(IntPtr point);
[DllImport("User32.dll", EntryPoint = "SendMessage")]
extern static int SendMessageGetTextLength(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
const uint WM_PASTE = 0x302;
const int WM_SETTEXT = 0X000C;
const int WM_GETTEXTLENGTH = 0x000E;
const int EM_SETSEL = 0x00B1;
const int EM_REPLACESEL = 0x00C2;
static void Main(string[] args)
{
//Abre o programa
Process prcss = new Process();
prcss.StartInfo.FileName = "excel.exe";
prcss.Start();
string aux = prcss.StartInfo.FileName;
//Verifica se o processo está a correr
Process[] processlist = Process.GetProcesses();
Code to write in it with the SendMessage().
case "2":
while (true)
{
//Testar com o SendMessage
Console.WriteLine("\nTexto: \n");
string texto = Console.ReadLine();
if (aux.Length == 0)
{
return;
}
if (prcss != null)
{
IntPtr notepadTextbox = FindWindowEx(prcss.MainWindowHandle, IntPtr.Zero, "edit", null);
int length = SendMessageGetTextLength(notepadTextbox, WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero);
if (!notepadTextbox.Equals(IntPtr.Zero))
{
//sending the message to the textbox
SendMessage(notepadTextbox, WM_SETTEXT, 0, texto);
SendMessage(notepadTextbox, EM_SETSEL, length, length);
SendMessage(notepadTextbox, EM_REPLACESEL, 1, texto + "\n");
}
}
Console.WriteLine("Sair? (S)im / (N)ão");
sair = Console.ReadLine();
if (sair == "s" || sair == "S")
{
IntPtr k = prcss.MainWindowHandle;
SetForegroundWindow(k);
prcss.Kill();
break;
}
}
I am currently developing a program that will send a "key press" (the letter A or 0x41 in virtual key codes) to another program (notepad) every X seconds.
The problem is that for it to work I need the other program (notepad) to be in the FOREGROUND, example :
Process[] processes = Process.GetProcessesByName("notepad");
foreach (Process proc in processes) {
SetForegroundWindow(proc.MainWindowHandle);
// Do Whatever
}
Thread.Sleep(1000);
Is there a way to do that WITHOUT notepad having to be in the foreground ?
Like something that could run in the background ?
You could do it via winApi. Try to use SendMessage
According to this link you can do following:
[DllImport("user32.dll")]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern IntPtr PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
public static void sendKeystroke(ushort k)
{
const uint WM_KEYDOWN = 0x100;
const uint WM_SYSCOMMAND = 0x018;
const uint SC_CLOSE = 0x053;
IntPtr WindowToFind = FindWindow(null, "Untitled1 - Notepad++");
IntPtr result3 = SendMessage(WindowToFind, WM_KEYDOWN, ((IntPtr)k), (IntPtr)0);
//IntPtr result3 = SendMessage(WindowToFind, WM_KEYUP, ((IntPtr)c), (IntPtr)0);
}
I am reading text from a notepad opened by my program.and this is my code
const int WM_GETTEXT = 0x000D;
const int WM_GETTEXTLENGTH = 0x000E;
[DllImport("User32.dll", EntryPoint = "SendMessage")]
extern static int SendMessageGetTextLength(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
[DllImport("User32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
extern static IntPtr SendMessageGetText(IntPtr hWnd, int msg, IntPtr wParam, [Out] StringBuilder lParam);
[DllImport("user32.dll", EntryPoint = "FindWindowEx")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
public static string GetText(IntPtr hwnd)
{
if (hwnd == IntPtr.Zero)
throw new ArgumentNullException("hwnd");
IntPtr handler = FindWindowEx(hwnd, new IntPtr(0), "Edit", null);
int length = SendMessageGetTextLength(handler, WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero);
if (length > 0 && length < int.MaxValue)
{
length++;
StringBuilder sb = new StringBuilder(length);
SendMessageGetText(handler, WM_GETTEXT, (IntPtr)sb.Length, sb);
return sb.ToString();
}
return String.Empty;
}
It is getting the text but in a special encoding.
For example, if the text entered is 'hello' it gets '興梀ȿڳㇺ'.
What is the encoding of this text so I can decode it to ASCII?
Your problem is in fact that you are passing sb.Length in the WM_GETTEXT message, when in fact you should be passing sb.Capacity or even just length.
I would do it like this:
if (length > 0 && length < int.MaxValue)
{
StringBuilder sb = new StringBuilder(length+1);
SendMessageGetText(handler, WM_GETTEXT, (IntPtr)length+1, sb);
return sb.ToString();
}
I'd also point out that WM_GETTEXT will not return more than 64k characters to the length < int.MaxValue isn't what you need.
Of course, in the longer run it may be better to use the Unicode throughout so that you can support international text.
I personally would always opt for using the Unicode APIs and use the following p/invoke declarations:
[DllImport("User32.dll", EntryPoint = "SendMessage",
CharSet = CharSet.Unicode, SetLastError = true)]
extern static int SendMessageGetTextLength(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
[DllImport("User32.dll", EntryPoint = "SendMessage",
CharSet = CharSet.Unicode, SetLastError = true)]
extern static IntPtr SendMessageGetText(IntPtr hWnd, int msg, IntPtr wParam, StringBuilder lParam);
[DllImport("user32.dll", EntryPoint = "FindWindowEx",
CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
Since you're writing in managed code, you may as well use the managed code automation interfaces, which does all the interop for you. Why reinvent the wheel?
using System.Windows.Automation;
public static string GetText(IntPtr hwnd)
{
IntPtr hwndEdit = FindWindowEx(hwnd, IntPtr.Zero, "Edit", null);
return (string)AutomationElement.FromHandle(hwndEdit).
GetCurrentPropertyValue(AutomationElement.NameProperty);
}
You can even make the automation do the FindWindowEx for you:
public static string GetText(IntPtr hwnd)
{
var editElement = AutomationElement.FromHandle(hwnd).
FindFirst(TreeScope.Subtree,
new PropertyCondition(
AutomationElement.ClassNameProperty, "Edit"));
return (string)editElement.GetCurrentPropertyValue(AutomationElement.NameProperty);
}
I am using the below code to close the window, by searching the window name in taskbar.
But i one case, my window will not appear in the taskbar. In that case, WM_Close could not close the window. Whats the other way to do it using WM_Close ???
void DaemonTerminamtionHook_KeyPressed(object sender, KeyPressedEventArgs e)
{
DaemonResult = MessageBox.Show("Are you sure, you want to Terminate Daemon?", "Terminate Daemon", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation);
if (DaemonResult == DialogResult.Yes)
{
//Free the resources of ShellBasics and terminate Daemon here.
IntPtr hWnd = FindWindowByCaption(IntPtr.Zero, "DAEMON TAB BAR");
bool ret = CloseWindow(hWnd);
}
}
//WM_Close
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
static uint WM_CLOSE = 0x10;
static bool CloseWindow(IntPtr hWnd)
{
SendMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
return true;
}
Now using the below code...But getting error in
"IntPtr hWnd = PostMessage(IntPtr.Zero, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);"
where to provide the window name in order to close that ???
void DaemonTerminamtionHook_KeyPressed(object sender, KeyPressedEventArgs e)
{
DaemonResult = MessageBox.Show("Are you sure, you want to Terminate Daemon?", "Terminate Daemon", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation);
if (DaemonResult == DialogResult.Yes)
{
IntPtr hWnd = PostMessage(IntPtr.Zero, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
bool ret = CloseWindow(hWnd);
}
}
static uint WM_CLOSE = 0x10;
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true)]
static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
static bool CloseWindow(IntPtr hWnd)
{
bool returnValue = PostMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
if (!returnValue)
throw new Win32Exception(Marshal.GetLastWin32Error());
return true;
}
Edit: Sorry misread your question.
Use FindWindow/FindWindowEx instead.