C# - Sending Windows API messages to ListView in Windows 7 - c#

Below is some code which works beautifully on Windows XP, but does not work on Windows 7. I'd like to know how to fix this for Windows 7, or perhaps a different approach...
I'm developing a C# application where I would like to launch a generic OpenFileDialog browser and have a particular file in the list pre-selected. As far as I can tell, the OpenFileDialog itself does not support selecting a particular file in the list, so I took to the Windows API to send messages directly to the handle of the ListView.
In UISpy, the ListView in the OpenFileDialog window is identified as ClassName=SysListView32 in Windows XP. In Windows 7 however this list is identified by ClassName=DUIListView. I cannot seem to find any documentation for the DUI library.
[DllImport("User32.dll")]
public static extern IntPtr FindWindow(String lpClassName, String lpWindowName);
[DllImport("user32.dll")]
private static extern int SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[StructLayoutAttribute(LayoutKind.Sequential)]
public struct LV_ITEM
{
public uint mask;
public int iItem;
public int iSubItem;
public uint state;
public uint stateMask;
public IntPtr pszText;
public int cchTextMax;
public int iImage;
public IntPtr lParam;
}
//get a handle to the ListView object
// findList is my own function which drills down through the UI tree for me
AutomationElement list = findList("Title of containing window", "Name of List in UISpy");
int handle_list = list.Current.NativeWindowHandle;
//some constants right out of CommCtrl.h
const int LVM_FIRST = 0x1000;
const int LVM_SETITEMSTATE = LVM_FIRST + 43;
const int LVIS_FOCUSED = 0x0001;
const int LVIS_SELECTED = 0x0002;
//build a LV_ITEM object to send in the API message
LV_ITEM lvi = new LV_ITEM();
lvi.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
lvi.state = LVIS_FOCUSED | LVIS_SELECTED;
//pick the index number of the file to select in the ListView
int wParam = 8;
IntPtr ptrLvi = Marshal.AllocHGlobal(Marshal.SizeOf(lvi));
Marshal.StructureToPtr(lvi, ptrLvi, false);
//send the message to the ListView!!
SendMessage(new IntPtr(handle_list), LVM_SETITEMSTATE, new IntPtr(wParam), ptrLvi);
Does anyone know the correct way to interface with the new DUIListView object? Does anyone know a different approach I can take to accomplish this task? Thank you so much.

Related

How to get the "Application Name" from hWnd for Windows 10 Store Apps (e.g. Edge)

I'm trying to get an understandable "Process Name" for Windows 10 apps. Currently, all of them use ApplicationFrameHost, so I thought I could use either the ModelId or the PackageName, but it seems Windows 10 Store Apps (I tried with Mail, Store and Edge) won't work with the Package query API
Using kernel32.dll, GetApplicationUserModelId returns APPMODEL_ERROR_NO_APPLICATION and GetPackageId returns APPMODEL_ERROR_NO_PACKAGE.
How can I get an identifier for a Windows 10 Store App, so that I can uniquely identify, say, Edge but also any other Windows 10 Store Apps?
Update
I'm getting the process ID from the hWnd (the window handle), so I think my problem is actually how to get the "real" process ID from a window handle. From there, using those methods would probably work.
UWP apps are wrapped into an other app/process. If this has focus, then try and find the child UWP process.
You will need some P/Invoke methods. Take a look at this class, which provide all the code you need to do the job:
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
namespace Stackoverflow
{
internal struct WINDOWINFO
{
public uint ownerpid;
public uint childpid;
}
public class UwpUtils
{
#region User32
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError = true)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
// When you don't want the ProcessId, use this overload and pass IntPtr.Zero for the second parameter
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);
/// <summary>
/// Delegate for the EnumChildWindows method
/// </summary>
/// <param name="hWnd">Window handle</param>
/// <param name="parameter">Caller-defined variable; we use it for a pointer to our list</param>
/// <returns>True to continue enumerating, false to bail.</returns>
public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);
[DllImport("user32", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumChildWindows(IntPtr hWndParent, EnumWindowProc lpEnumFunc, IntPtr lParam);
#endregion
#region Kernel32
public const UInt32 PROCESS_QUERY_INFORMATION = 0x400;
public const UInt32 PROCESS_VM_READ = 0x010;
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool QueryFullProcessImageName([In]IntPtr hProcess, [In]int dwFlags, [Out]StringBuilder lpExeName, ref int lpdwSize);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr OpenProcess(
UInt32 dwDesiredAccess,
[MarshalAs(UnmanagedType.Bool)]
Boolean bInheritHandle,
Int32 dwProcessId
);
#endregion
public static string GetProcessName(IntPtr hWnd)
{
string processName = null;
hWnd = GetForegroundWindow();
if (hWnd == IntPtr.Zero)
return null;
uint pID;
GetWindowThreadProcessId(hWnd, out pID);
IntPtr proc;
if ((proc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, (int)pID)) == IntPtr.Zero)
return null;
int capacity = 2000;
StringBuilder sb = new StringBuilder(capacity);
QueryFullProcessImageName(proc, 0, sb, ref capacity);
processName = sb.ToString(0, capacity);
// UWP apps are wrapped in another app called, if this has focus then try and find the child UWP process
if (Path.GetFileName(processName).Equals("ApplicationFrameHost.exe"))
{
processName = UWP_AppName(hWnd, pID);
}
return processName;
}
#region Get UWP Application Name
/// <summary>
/// Find child process for uwp apps, edge, mail, etc.
/// </summary>
/// <param name="hWnd">hWnd</param>
/// <param name="pID">pID</param>
/// <returns>The application name of the UWP.</returns>
private static string UWP_AppName(IntPtr hWnd, uint pID)
{
WINDOWINFO windowinfo = new WINDOWINFO();
windowinfo.ownerpid = pID;
windowinfo.childpid = windowinfo.ownerpid;
IntPtr pWindowinfo = Marshal.AllocHGlobal(Marshal.SizeOf(windowinfo));
Marshal.StructureToPtr(windowinfo, pWindowinfo, false);
EnumWindowProc lpEnumFunc = new EnumWindowProc(EnumChildWindowsCallback);
EnumChildWindows(hWnd, lpEnumFunc, pWindowinfo);
windowinfo = (WINDOWINFO)Marshal.PtrToStructure(pWindowinfo, typeof(WINDOWINFO));
IntPtr proc;
if ((proc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, (int)windowinfo.childpid)) == IntPtr.Zero)
return null;
int capacity = 2000;
StringBuilder sb = new StringBuilder(capacity);
QueryFullProcessImageName(proc, 0, sb, ref capacity);
Marshal.FreeHGlobal(pWindowinfo);
return sb.ToString(0, capacity);
}
/// <summary>
/// Callback for enumerating the child windows.
/// </summary>
/// <param name="hWnd">hWnd</param>
/// <param name="lParam">lParam</param>
/// <returns>always <c>true</c>.</returns>
private static bool EnumChildWindowsCallback(IntPtr hWnd, IntPtr lParam)
{
WINDOWINFO info = (WINDOWINFO)Marshal.PtrToStructure(lParam, typeof(WINDOWINFO));
uint pID;
GetWindowThreadProcessId(hWnd, out pID);
if (pID != info.ownerpid)
info.childpid = pID;
Marshal.StructureToPtr(info, lParam, true);
return true;
}
#endregion
}
}
Now, get a handle to the current foreground window using another P/Invoke method
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
Use the return value and call the GetProcessName method from the code above. You should receive the correct name/path to the process.
Here is a simple Form to test the code:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using StackOverflow;
namespace Stackoverflow.Test
{
public partial class TestForm : Form
{
WinEventDelegate dele = null;
delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
[DllImport("user32.dll")]
static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
private const uint WINEVENT_OUTOFCONTEXT = 0;
private const uint EVENT_SYSTEM_FOREGROUND = 3;
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
public TestForm()
{
InitializeComponent();
dele = new WinEventDelegate(WinEventProc);
IntPtr m_hhook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, dele, 0, 0, WINEVENT_OUTOFCONTEXT);
}
public void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
textBox1.AppendText(GetActiveWindowTitle() + "\n");
}
private string GetActiveWindowTitle()
{
return UwpUtils.GetProcessName(GetForegroundWindow());
}
}
}
You can download the full code, including the example/test on GitHub.
You can use GetPackageId() and then PackageFullNameFromId().
E.g.:
HANDLE hProcess = OpenProcess(
PROCESS_QUERY_LIMITED_INFORMATION,
false,
pe32.th32ProcessID);
UINT32 bufferLength = 0;
LONG result = GetPackageId(hProcess, &bufferLength, nullptr);
BYTE* buffer = (PBYTE) malloc(bufferLength);
result = GetPackageId(hProcess, &bufferLength, buffer);
PACKAGE_ID* packageId = reinterpret_cast<PACKAGE_ID*>(buffer);
wprintf(L"Name: %s\n", packageId->name);
GetPackageFullName/FamilyName/Id(hprocess,...) etc return APPMODEL_ERROR_NO_PACKAGE if the process has no package identity. Ditto GetApplicationUserModelId(hprocess...) returns APPMODEL_ERROR_NO_APPLICATION because likewise the process has no application identity.
Sounds like you have an HWND for a process that does work on behalf of the application, but is not the application. This is quite common - RuntimeBroker and other processes run as 'Desktop apps' (i.e. process w/o package or application identity) as brokers to do things for application processes which they can't do for themselves.
To your original question, "I'm getting the process ID from the hWnd (the window handle), so I think my problem is actually how to get the "real" process ID from a window handle" this is a fundamentally flawed approach. You have a pid from an HWND, but if the process is a broker it can do work on behalf of multiple applications - the broker process has no identity; it knows *per request/WinRT API call/etc who its caller is and scopes its work to that identity. You can't discover that at the process level.
So first of all there is a thing called AppUserModelID, it's ID of window that is used by taskbar to group windows. Because all WinRT windows are from same process but they aren't grouped, it means that each app has own UserModelID.
To get UserModelID from HWND you can use method from this answer.
#include "Propsys.h"
#include <propkey.h>
#pragma comment (lib, "Shell32.lib")
//.........
IPropertyStore* propStore;
auto weatherWnd = FindWindow(L"ApplicationFrameWindow", L"Weather");
SHGetPropertyStoreForWindow(weatherWnd, IID_IPropertyStore, (void**)&propStore);
PROPVARIANT prop;
propStore->GetValue(PKEY_AppUserModel_ID, &prop);
And prop will contain value LPWSTR = 0x00838f68 L"Microsoft.BingWeather_8wekyb3d8bbwe!App". This is full entry point name in format <FullPackageFamilyName>!<EntryPoint>. Entry point for launched apps usually called App. Entry points are defined in app manifest.
Also interesting thing - child window that is owned by app is not destroyed, but is moved away from app frame host into desktop window. I don't know why it happens, but you must be careful because FindWindow(nullptr, L"Weather") returned child app window and not appframehost window.
P.S. AppUserModelID is just a string and it's format is not documented, so this method is not exactly the most reliable one.
P.P.S. Also I noticed that you want to have icon and name, you can use PackageManager for that, it requires you to reference winmd assembly, how to do this look here
Below is a similar one for getting the actual process name,
Name of process for active window in Windows 8/10
With Spy++ utility, confirmed that Windows.Core.UI.CoreWindow is a child window of Weather and it is the one that we are interested in. (Verified on Win10 10563)

RegisterPowerSettingsNotification C# pinvoke

I am trying to detect when the laptop lid opens and closes, should be real simple. I can register that event properly it seems, but then I don't get notification when I close my laptop window.
Here's the DLL Import
(DLL code: http://www.pinvoke.net/default.aspx/user32/registerpowersettingnotification.html )
(GUID_LIDCLOSE_ACTION: http://social.msdn.microsoft.com/Forums/en-US/tabletandtouch/thread/0bbf90be-9322-47fb-bfa4-016b57211b3a )
[DllImport(#"User32", SetLastError = true,
EntryPoint = "RegisterPowerSettingNotification",
CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr RegisterPowerSettingNotification(
IntPtr hRecipient,
ref Guid PowerSettingGuid,
Int32 Flags);
static Guid GUID_LIDCLOSE_ACTION =
new Guid(0xBA3E0F4D, 0xB817, 0x4094, 0xA2, 0xD1,
0xD5, 0x63, 0x79, 0xE6, 0xA0, 0xF3);
private const int WM_POWERBROADCAST = 0x0218;
private const int DEVICE_NOTIFY_WINDOW_HANDLE = 0x00000000;
const int PBT_POWERSETTINGCHANGE = 0x8013; // DPPE
[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct POWERBROADCAST_SETTING
{
public Guid PowerSetting;
public uint DataLength;
public byte Data;
}
And then here is how I am registering the GUID_LIDCLOSE_ACTION event:
private void registerLidClosedNotification()
{
IntPtr hWnd = this.Handle;
IntPtr ret = RegisterPowerSettingNotification(hWnd,
ref GUID_LIDCLOSE_ACTION,
DEVICE_NOTIFY_WINDOW_HANDLE);
Debug.WriteLine("Registered: " + ret.ToString());
Debug.WriteLIne("LastError:" + Marshal.GetLastWin32Error().ToString());
}
Here's the output from that:
Registered: 6867560
LastError:0
Looks good to me.
Then where I'm supposed to recieve the message:
private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
Debug.WriteLine("Entered: WndProc"); // we never make it even this far!
So why isn't it making it to the WndProc function if it is registered :[
I think what you've done with the RegisterPowerSettingNotification is cause Windows to include messages for WM_POWERBROADCAST to your WndProc, but you still need to actually hook the WndProc as well.
It looks like you're using WinForms (because your sample has "this.Handle"), in which case you can just override the protected WndProc method on your Form.
If you're using WPF, then you can do this by getting an HwndSource for the root Window and then calling AddHook.
You'll want to read up on the documentation about the WndProc in WinForms (http://msdn.microsoft.com/en-us/library/system.windows.forms.form.wndproc.aspx). It will get called a lot, so you'll want to only run your code when it's the message you care about, and unconditionally call base.WndProc.

SetText of textbox in external app. Win32 API

Using Winspector I've found out the ID of the child textbox I want to change is 114. Why isn't this code changing the text of the TextBox?
[DllImport("user32.dll")]
static extern IntPtr GetDlgItem(IntPtr hDlg, int nIDDlgItem);
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int msg, int Param, string s);
const int WM_SETTEXT = 0x000c;
private void SetTextt(IntPtr hWnd, string text)
{
IntPtr boxHwnd = GetDlgItem(hWnd, 114);
SendMessage(boxHwnd, WM_SETTEXT, 0, text);
}
The following is what I've used successfully for that purpose w/ my GetLastError error checking removed/disabled:
[DllImport("user32.dll", SetLastError = false)]
public static extern IntPtr GetDlgItem(IntPtr hDlg, int nIDDlgItem);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
public static extern IntPtr SendMessage(HandleRef hWnd, uint Msg, IntPtr wParam, string lParam);
public const uint WM_SETTEXT = 0x000C;
private void InteropSetText(IntPtr iptrHWndDialog, int iControlID, string strTextToSet)
{
IntPtr iptrHWndControl = GetDlgItem(iptrHWndDialog, iControlID);
HandleRef hrefHWndTarget = new HandleRef(null, iptrHWndControl);
SendMessage(hrefHWndTarget, WM_SETTEXT, IntPtr.Zero, strTextToSet);
}
I've tested this code and it works, so if it fails for you, you need to be sure that you are using the right window handle (the handle of the Dialog box itself) and the right control ID. Also try something simple like editing the Find dialog in Notepad.
I can't comment yet in the post regarding using (char *) but it's not necessary. See the second C# overload in p/Invoke SendMessage. You can pass String or StringBuilder directly into SendMessage.
I additionally note that you say that your control ID is 114. Are you certain WinSpector gave you that value in base 10? Because you are feeding it to GetDlgItem as a base 10 number. I use Spy++ for this and it returns control IDs in base 16. In that case you would use:
IntPtr boxHwnd = GetDlgItem(hWnd, 0x0114);
Please convert your control id (obtained from spy ++) from Hexdecimal Number to Decimal Number and pass that value to the GetDlgItem function.With this
you will get the handle of Text box.This worked for me.
[DllImport("user32.dll")]
static extern IntPtr GetDlgItem(IntPtr hDlg, int nIDDlgItem);
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int msg, int Param, string s);
const int WM_SETTEXT = 0x000c;
private void SetTextt(IntPtr hWnd, string text)
{
IntPtr boxHwnd = GetDlgItem(hWnd, 114);
SendMessage(boxHwnd, WM_SETTEXT, 0, text);
}
Are you sure you are passing text right? SendMessage last param should be a pointer to char* containing text you want to set.
Look at my "crude hack" of setting text in
How to get selected cells from TDBGrid in Delphi 5
this is done in Delphi 5, where PChar is char* alias, and I simply cast it as int (Integer in Delphi).
You must make sure that "text" is allocated in the external app's memory space. You will not be able to allocate text in the caller app and pass it to another app as each of them will have their own private memory space.

How to invoke the screen saver in Windows in C#?

I'd like to invoke the user's screen saver if such is defined, in a Windows environment.
I know it can be done using pure C++ code (and then the wrapping in C# is pretty simple), as suggested here.
Still, for curiosity, I'd like to know if such task can be accomplished by purely managed code using the dot net framework (version 2.0 and above), without p/invoke and without visiting the C++ side (which, in turn, can use windows API pretty easily).
I've an idea, I'm not sure how consistently this would work, so you'd need to research a bit I think, but hopefully it's enough to get you started.
A screen saver is just an executable, and the registry stores the location of this executable in HKCU\Control Panel\Desktop\SCRNSAVE.EXE
On my copy of Vista, this worked for me:
RegistryKey screenSaverKey = Registry.CurrentUser.OpenSubKey(#"Control Panel\Desktop");
if (screenSaverKey != null)
{
string screenSaverFilePath = screenSaverKey.GetValue("SCRNSAVE.EXE", string.Empty).ToString();
if (!string.IsNullOrEmpty(screenSaverFilePath) && File.Exists(screenSaverFilePath))
{
Process screenSaverProcess = Process.Start(new ProcessStartInfo(screenSaverFilePath, "/s")); // "/s" for full-screen mode
screenSaverProcess.WaitForExit(); // Wait for the screensaver to be dismissed by the user
}
}
I think having a .Net library function that does this is highly unlikely - I'm not aware of any. A quick search returned this Code Project tutorial which contains an example of a managed wrapper which you mentioned in your question.
P/invoke exists so that you're able to access OS-specific features, of which screen savers are an example.
I'm not sure you can use completely managed code to do this.
This uses Windows API but is still very simple: Launch System Screensaver from C# Windows Form
Working on any version of windows...
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace HQ.Util.Unmanaged
{
public class ScreenSaverHelper
{
[DllImport("User32.dll")]
public static extern int SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", EntryPoint = "GetDesktopWindow")]
private static extern IntPtr GetDesktopWindow();
// Signatures for unmanaged calls
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool SystemParametersInfo(int uAction, int uParam, ref int lpvParam, int flags);
// Constants
private const int SPI_GETSCREENSAVERACTIVE = 16;
private const int SPI_SETSCREENSAVERACTIVE = 17;
private const int SPI_GETSCREENSAVERTIMEOUT = 14;
private const int SPI_SETSCREENSAVERTIMEOUT = 15;
private const int SPI_GETSCREENSAVERRUNNING = 114;
private const int SPIF_SENDWININICHANGE = 2;
private const uint DESKTOP_WRITEOBJECTS = 0x0080;
private const uint DESKTOP_READOBJECTS = 0x0001;
private const int WM_CLOSE = 16;
public const uint WM_SYSCOMMAND = 0x112;
public const uint SC_SCREENSAVE = 0xF140;
public enum SpecialHandles
{
HWND_DESKTOP = 0x0,
HWND_BROADCAST = 0xFFFF
}
public static void TurnScreenSaver(bool turnOn = true)
{
// Does not work on Windows 7
// int nullVar = 0;
// SystemParametersInfo(SPI_SETSCREENSAVERACTIVE, 1, ref nullVar, SPIF_SENDWININICHANGE);
// Does not work on Windows 7, can't broadcast. Also not needed.
// SendMessage(new IntPtr((int) SpecialHandles.HWND_BROADCAST), WM_SYSCOMMAND, SC_SCREENSAVE, 0);
SendMessage(GetDesktopWindow(), WM_SYSCOMMAND, (IntPtr)SC_SCREENSAVE, (IntPtr)0);
}
}
}

C# - Sending messages to Google Chrome from C# application

I've been searching around, and I haven't found how I would do this from C#.
I was wanting to make it so I could tell Google Chrome to go Forward, Back, Open New Tab, Close Tab, Open New Window, and Close Window from my C# application.
I did something similar with WinAmp using
[DllImport("user32", EntryPoint = "SendMessageA")]
private static extern int SendMessage(int Hwnd, int wMsg, int wParam, int lParam);
and a a few others. But I don't know what message to send or how to find what window to pass it to, or anything.
So could someone show me how I would send those 6 commands to Chrome from C#? thanks
EDIT:
Ok, I'm getting voted down, so maybe I wasn't clear enough, or people are assuming I didn't try to figure this out on my own.
First off, I'm not very good with the whole DllImport stuff. I'm still learning how it all works.
I found how to do the same idea in winamp a few years ago, and I was looking at my code. I made it so I could skip a song, go back, play, pause, and stop winamp from my C# code. I started by importing:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr FindWindow([MarshalAs(UnmanagedType.LPTStr)] string lpClassName, [MarshalAs(UnmanagedType.LPTStr)] string lpWindowName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern int SendMessageA(IntPtr hwnd, int wMsg, int wParam, uint lParam);
[DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
public static extern int GetWindowText(IntPtr hwnd, string lpString, int cch);
[DllImport("user32", EntryPoint = "FindWindowExA")]
private static extern int FindWindowEx(int hWnd1, int hWnd2, string lpsz1, string lpsz2);
[DllImport("user32", EntryPoint = "SendMessageA")]
private static extern int SendMessage(int Hwnd, int wMsg, int wParam, int lParam);
Then the code I found to use this used these constants for the messages I send.
const int WM_COMMAND = 0x111;
const int WA_NOTHING = 0;
const int WA_PREVTRACK = 40044;
const int WA_PLAY = 40045;
const int WA_PAUSE = 40046;
const int WA_STOP = 40047;
const int WA_NEXTTRACK = 40048;
const int WA_VOLUMEUP = 40058;
const int WA_VOLUMEDOWN = 40059;
const int WINAMP_FFWD5S = 40060;
const int WINAMP_REW5S = 40061;
I would get the hwnd (the program to send the message to) by:
IntPtr hwnd = FindWindow(m_windowName, null);
then I would send a message to that program:
SendMessageA(hwnd, WM_COMMAND, WA_STOP, WA_NOTHING);
I assume that I would do something very similar to this for Google Chrome. but I don't know what some of those values should be, and I googled around trying to find the answer, but I couldn't, which is why I asked here. So my question is how do I get the values for:
m_windowName and WM_COMMAND
and then, the values for the different commands, forward, back, new tab, close tab, new window, close window?
Start your research at http://dev.chromium.org/developers
EDIT: Sending a message to a window is only half of the work. The window has to respond to that message and act accordingly. If that window doesn't know about a message or doesn't care at all you have no chance to control it by sending window messages.
You're looking at an implementation detail on how you remote controlled Winamp. Sending messages is just one way to do it and it's the way the Winamp developers chose. Those messages you're using are user defined messages that have a specific meaning only to Winamp.
What you have to do in the first step is to find out if Chromium supports some kind of remote controlling and what those mechanisms are.
You can get the window name easily using Visual Studio's Spy++ and pressing CTRL+F, then finding chrome. I tried it and got
"Chrome_VistaFrame" for the out window. The actual window with the webpage in is "Chrome_RenderWidgetHostHWND".
As far as WM_COMMAND goes - you'll need to experiment. You'll obviously want to send button clicks (WM_MOUSEDOWN of the top off my head). As the back,forward buttons aren't their own windows, you'll need to figure out how to do this with simulating a mouse click at a certain x,y position so chrome knows what you're doing. Or you could send the keyboard shortcut equivalent for back/forward and so on.
An example I wrote a while ago does this with trillian and winamp: sending messages to windows via c# and winapi
There's also tools out there to macro out this kind of thing already, using a scripting language - autoit is one I've used: autoit.com
Ok, here's what I've got so far... I kinda know what I need to do, but it's just a matter of doing it now...
Here's the window from Spy++, I locked onto the Chrome_RenderWidgetHostHWND and clicked the Back button on my keyboard. Here's what I got:
So here's my assumptions, and I've been playing with this forever now, I just can't figure out the values.
IntPtr hWnd = FindWindow("Chrome_RenderWidgetHostHWND", null);
SendMessage(hWnd, WM_KEYDOWN, VK_BROWSER_BACK, 0);
SendMessage(hWnd, WM_KEYUP, VK_BROWSER_BACK, 0);
Now, I just don't know what I should make the WM_KEYDOWN/UP values or the VK_BROWSER_BACK/FORWARD values...
I tried this:
const int WM_KEYDOWN = 0x100;
const int WM_KEYUP = 0x101;
const int VK_BROWSER_BACK = 0x6A;
const int VK_BROWSER_FORWARD = 0x69;
The latter two values I got from the image I just showed, the ScanCodes for those two keys. I don't know if I did it right though. The former two values I got after searching google for the WM_KEYDOWN value, and someone used &H100 and &H101 for the two values. I've tried several other random ideas I've seen floating around. I just can't figure this out.
Oh, and here's the SendMessage method
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam, uint lParam);
This is a great site for interop constants:
pinvoke
Another way of finding the values is to search koders.com, using C# as the language, for WM_KEYDOWN or the constant you're after:
Koders.com search
&H values look like that's from VB(6). pinvoke and koders both return results for VK_BROWSER_FORWARD,
private const UInt32 WM_KEYDOWN = 0x0100;
private const UInt32 WM_KEYUP = 0x0101;
public const ushort VK_BROWSER_BACK = 0xA6;
public const ushort VK_BROWSER_FORWARD = 0xA7;
public const ushort VK_BROWSER_REFRESH = 0xA8;
public const ushort VK_BROWSER_STOP = 0xA9;
public const ushort VK_BROWSER_SEARCH = 0xAA;
public const ushort VK_BROWSER_FAVORITES = 0xAB;
public const ushort VK_BROWSER_HOME = 0xAC;
(It's funny how many wrong defintions of VK constants are floating about, considering VK_* are 1 byte 0-255 values, and people have made them uints).
Looks slightly different from your consts. I think the function you're after is SendInput (but I haven't tried it) as it's a virtual key.
[DllImport("User32.dll")]
private static extern uint SendInput(uint numberOfInputs, [MarshalAs(UnmanagedType.LPArray, SizeConst = 1)] KEYBOARD_INPUT[] input, int structSize);
Explanation about the parameters:
Parameters
nInputs- Number of structures in the pInputs array.
pInputs - Pointer to an array of INPUT structures. Each structure represents an event to be inserted into the keyboard or mouse input stream.
cbSize - Specifies the size, in bytes, of an INPUT structure. If cbSize is not the size of an INPUT structure, the function fails.
This needs a KEYBOARD_INPUT type:
[StructLayout(LayoutKind.Sequential)]
public struct KEYBOARD_INPUT
{
public uint type;
public ushort vk;
public ushort scanCode;
public uint flags;
public uint time;
public uint extrainfo;
public uint padding1;
public uint padding2;
}
And finally a sample, which I haven't tested if it works:
/*
typedef struct tagKEYBDINPUT {
WORD wVk;
WORD wScan;
DWORD dwFlags;
DWORD time;
ULONG_PTR dwExtraInfo;
} KEYBDINPUT, *PKEYBDINPUT;
*/
public static void sendKey(int scanCode, bool press)
{
KEYBOARD_INPUT[] input = new KEYBOARD_INPUT[1];
input[0] = new KEYBOARD_INPUT();
input[0].type = INPUT_KEYBOARD;
input[0].vk = VK_BROWSER_BACK;
uint result = SendInput(1, input, Marshal.SizeOf(input[0]));
}
Also you'll need to focus the Chrome window using SetForegroundWindow

Categories

Resources