Okay, so basically I want to be able to retrieve keyboard text. Like entering text into a text field or something. I'm only writing my game for windows.
I've disregarded using Guide.BeginShowKeyboardInput because it breaks the feel of a self contained game, and the fact that the Guide always shows XBOX buttons doesn't seem right to me either. Yes it's the easiest way, but I don't like it.
Next I tried using System.Windows.Forms.NativeWindow. I created a class that inherited from it, and passed it the Games window handle, implemented the WndProc function to catch WM_CHAR (or WM_KEYDOWN) though the WndProc got called for other messages, WM_CHAR and WM_KEYDOWN never did. So I had to abandon that idea, and besides, I was also referencing the whole of Windows forms, which meant unnecessary memory footprint bloat.
So my last idea was to create a Thread level, low level keyboard hook. This has been the most successful so far. I get WM_KEYDOWN message, (not tried WM_CHAR yet) translate the virtual keycode with Win32 funcation MapVirtualKey to a char. And I get my text! (I'm just printing with Debug.Write at the moment)
A couple problems though. It's as if I have caps lock on, and an unresponsive shift key. (Of course it's not however, it's just that there is only one Virtual Key Code per key, so translating it only has one output) and it adds overhead as it attaches itself to the Windows Hook List and isn't as fast as I'd like it to be, but the slowness could be more due to Debug.Write.
Has anyone else approached this and solved it, without having to resort to an on screen keyboard? or does anyone have further ideas for me to try?
thanks in advance.
Question asked by Jimmy
Maybe I'm not understanding the question, but why can't you use the XNA Keyboard and KeyboardState classes?
My comment:
It's because though you can read keystates, you can't get access to typed text as and how it is typed by the user.
So let me further clarify. I want to implement being able to read text input from the user as if they are typing into textbox is windows. The keyboard and KeyboardState class get states of all keys, but I'd have to map each key and combination to it's character representation. This falls over when the user doesn't use the same keyboard language as I do especially with symbols (my double quotes is shift + 2, while american keyboards have theirs somewhere near the return key).
it seems my window hook was the way to go, just the reason I wasn't getting WM_CHAR is because the XNA message pump doesn't do translate message.
Adding TranslateMessage in whenever I received a WM_KEYDOWN message meant I got my WM_CHAR message, I then used this to fire a character typed event in my MessageHook class which my KeyboardBuffer class had subscribed to, which then buffers that to a text buffer :D (or StringBuilder, but the result is the same)
So I have it all working as I want.
Many thanks to Jimmy for a link to a very informative thread.
For adding a windows hook in XNA
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Reflection;
/* Author: Sekhat
*
* License: Public Domain.
*
* Usage:
*
* Inherit from this class, and override the WndProc function in your derived class,
* in which you handle your windows messages.
*
* To start recieving the message, create an instance of your derived class, passing in the
* window handle of the window you want to listen for messages for.
*
* in XNA: this would be the Game.Window.Handle property
* in Winforms Form.Handle property
*/
namespace WindowsHookExample
{
public abstract class WindowsHook : IDisposable
{
IntPtr hHook;
IntPtr hWnd;
// Stored here to stop it from getting garbage collected
Win32.WndProcDelegate wndProcDelegate;
public WindowsHook(IntPtr hWnd)
{
this.hWnd = hWnd;
wndProcDelegate = WndProcHook;
CreateHook();
}
~WindowsHook()
{
Dispose(false);
}
private void CreateHook()
{
uint threadId = Win32.GetWindowThreadProcessId(hWnd, IntPtr.Zero);
hHook = Win32.SetWindowsHookEx(Win32.HookType.WH_CALLWNDPROC, wndProcDelegate, IntPtr.Zero, threadId);
}
private int WndProcHook(int nCode, IntPtr wParam, ref Win32.Message lParam)
{
if (nCode >= 0)
{
Win32.TranslateMessage(ref lParam); // You may want to remove this line, if you find your not quite getting the right messages through. This is here so that WM_CHAR is correctly called when a key is pressed.
WndProc(ref lParam);
}
return Win32.CallNextHookEx(hHook, nCode, wParam, ref lParam);
}
protected abstract void WndProc(ref Win32.Message message);
#region Interop Stuff
// I say thankya to P/Invoke.net.
// Contains all the Win32 functions I need to deal with
protected static class Win32
{
public enum HookType : int
{
WH_JOURNALRECORD = 0,
WH_JOURNALPLAYBACK = 1,
WH_KEYBOARD = 2,
WH_GETMESSAGE = 3,
WH_CALLWNDPROC = 4,
WH_CBT = 5,
WH_SYSMSGFILTER = 6,
WH_MOUSE = 7,
WH_HARDWARE = 8,
WH_DEBUG = 9,
WH_SHELL = 10,
WH_FOREGROUNDIDLE = 11,
WH_CALLWNDPROCRET = 12,
WH_KEYBOARD_LL = 13,
WH_MOUSE_LL = 14
}
public struct Message
{
public IntPtr lparam;
public IntPtr wparam;
public uint msg;
public IntPtr hWnd;
}
/// <summary>
/// Defines the windows proc delegate to pass into the windows hook
/// </summary>
public delegate int WndProcDelegate(int nCode, IntPtr wParam, ref Message m);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr SetWindowsHookEx(HookType hook, WndProcDelegate callback,
IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, ref Message m);
[DllImport("coredll.dll", SetLastError = true)]
public static extern IntPtr GetModuleHandle(string module);
[DllImport("user32.dll", EntryPoint = "TranslateMessage")]
public extern static bool TranslateMessage(ref Message m);
[DllImport("user32.dll")]
public extern static uint GetWindowThreadProcessId(IntPtr window, IntPtr module);
}
#endregion
#region IDisposable Members
public void Dispose()
{
Dispose(true);
}
private void Dispose(bool disposing)
{
if (disposing)
{
// Free managed resources here
}
// Free unmanaged resources here
if (hHook != IntPtr.Zero)
{
Win32.UnhookWindowsHookEx(hHook);
}
}
#endregion
}
}
I used the solution from this gamedev.net post and it works great :)
Here's a simple way, IMO, to have the space, back, A-Z and then the special chars !,#,#,$,%,^,&,*,(,). (Note, you need to import System.Linq) Here are the fields:
Keys[] keys;
bool[] IskeyUp;
string[] SC = { ")" , "!", "#", "#", "$", "%", "^", "&", "*", "("};//special characters
Constructor:
keys = new Keys[38];
Keys[] tempkeys;
tempkeys = Enum.GetValues(typeof(Keys)).Cast<Keys>().ToArray<Keys>();
int j = 0;
for (int i = 0; i < tempkeys.Length; i++)
{
if (i == 1 || i == 11 || (i > 26 && i < 63))//get the keys listed above as well as A-Z
{
keys[j] = tempkeys[i];//fill our key array
j++;
}
}
IskeyUp = new bool[keys.Length]; //boolean for each key to make the user have to release the key before adding to the string
for (int i = 0; i < keys.Length; i++)
IskeyUp[i] = true;
And Finally, the update method:
string result = "";
public override void Update(GameTime gameTime)
{
KeyboardState state = Keyboard.GetState();
int i = 0;
foreach (Keys key in keys)
{
if (state.IsKeyDown(key))
{
if (IskeyUp[i])
{
if (key == Keys.Back && result != "") result = result.Remove(result.Length - 1);
if (key == Keys.Space) result += " ";
if (i > 1 && i < 12)
{
if (state.IsKeyDown(Keys.RightShift) || state.IsKeyDown(Keys.LeftShift))
result += SC[i - 2];//if shift is down, and a number is pressed, using the special key
else result += key.ToString()[1];
}
if (i > 11 && i < 38)
{
if (state.IsKeyDown(Keys.RightShift) || state.IsKeyDown(Keys.LeftShift))
result += key.ToString();
else result += key.ToString().ToLower(); //return the lowercase char is shift is up.
}
}
IskeyUp[i] = false; //make sure we know the key is pressed
}
else if (state.IsKeyUp(key)) IskeyUp[i] = true;
i++;
}
base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
}
Hopefull this well help. I personally thought it was easier to use than the hooks, and this also can easily be modified for special results (such as shuffling the keys).
Maybe I'm not understanding the question, but why can't you use the XNA Keyboard and KeyboardState classes?
This page is on top of google result about WM_CHAR interception in xna, so i leave here some note. Maybe this will be useful for other (if they will be able to understand my English =)) ).
I try code with windowshook from Sekhat, but seems there should be WH_GETMESSAGE passed to SetWindowsHookEx instead Win32.HookType.WH_CALLWNDPROC (only with WH_GETMESSAGE code lparaw will point to Win32.Message ).
Also there is sometime duplicated messages (with wparam 0).
(look here - http://msdn.microsoft.com/en-us/library/ms644981%28v=VS.85%29.aspx
something about PM_NOREMOVE/PM_REMOVE in WPARAM )
When i add something like this
if (nCode >= 0 && wParam == 1)
{
Win32.TranslateMessage(ref lParam);
WndProc(ref lParam);
}
wm_keypress wm_char duplication stopped (i supose 1 is PM_NOREMOVE or PM_REMOVE).
P.S.
nuclex variant now show 404 page, but can be viewed with webarchive.
nuclex variant works, but it cause broken mouseWheel processing from native XNA MouseState (on XNA 3.1) =(
Related
I have a window (subclass of System.Windows.Forms.Form) without a border in order to apply a custom style to it. Moving the window when the mouse button is down seems to be easy. Either by sending a WM_NCLBUTTONDOWN HT_CAPTION or a WM_SYSCOMMAND 0xF102 message the window can be dragged to a new location. As soon as the mouse button is up though, it seems to be impossible to move the window.
One could send WM_SYSCOMMAND SC_MOVE message but then the cursor moves at the top center of the window and awaits for the user to press any arrow key in order to hook the window for move -which is awkward at the least. I tried to fake a key press/release sequence but that of course didn't work as I called SendMessage with the current form Handle as argument but I guess the message should not be sent to the current form.
The desired behavior is: click a button (ie the mouse button is released) move the form where cursor goes, click again to release the form. Is that possible with winapi? Unfortunately I am not familiar with it.
Addendum
Sending a key input: I tried to use SendInput as SendMessage is supposed to be bad practice. Still it didn't hook the window. I tried to read and print the winapi error code with Marshal.GetLastWin32Error() and I got a 5 which is access denied. The curious thing was that I received the messages after the move sequence ended (ie I manually pressed a key or mouse button). No idea how to work around this.
Using the IMessageFilter (IVSoftware's answer): this is what I ended up doing but that has 2 issues: moving the window with its Location property has lag compared to the native way (no big deal for now) and also it doesn't receive mouse messages that occur outside the main form. That means it won't work a. for multiscreen environments b. if the cursor moves outside the forms of the application. I could create full screen transparent forms for every monitor that will serve as an "message canvas" but still... why not give the OS way a chance.
As I understand it, the desired behavior is to enable the "Click to Move" (one way or another) and then click anywhere on a multiscreen surface and have the borderless form follow the mouse to the new position. One solution that seems to work in my brief testing is to pinvoke the WinApi SetWindowsHookEx to install a global low level hook for WH_MOUSE_LL in order to intercept WM_LBUTTONDOWN.
*This answer has been modified in order to track updates to the question.
Low-level global mouse hook
public MainForm()
{
InitializeComponent();
using (var process = Process.GetCurrentProcess())
{
using (var module = process.MainModule!)
{
var mname = module.ModuleName!;
var handle = GetModuleHandle(mname);
_hook = SetWindowsHookEx(
HookType.WH_MOUSE_LL,
lpfn: callback,
GetModuleHandle(mname),
0);
}
}
// Unhook when this `Form` disposes.
Disposed += (sender, e) => UnhookWindowsHookEx(_hook);
// A little hack to keep window on top while Click-to-Move is enabled.
checkBoxEnableCTM.CheckedChanged += (sender, e) =>
{
TopMost = checkBoxEnableCTM.Checked;
};
// Compensate move offset with/without the title NC area.
var offset = RectangleToScreen(ClientRectangle);
CLIENT_RECT_OFFSET = offset.Y - Location.Y;
}
readonly int CLIENT_RECT_OFFSET;
IntPtr _hook;
private IntPtr callback(int code, IntPtr wParam, IntPtr lParam)
{
var next = IntPtr.Zero;
if (code >= 0)
{
switch ((int)wParam)
{
case WM_LBUTTONDOWN:
if (checkBoxEnableCTM.Checked)
{
_ = onClickToMove(MousePosition);
// This is a very narrow condition and the window is topmost anyway.
// So probably swallow this mouse click and skip other hooks in the chain.
return (IntPtr)1;
}
break;
}
}
return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
}
}
Perform the move
private async Task onClickToMove(Point mousePosition)
{
// Exempt clicks that occur on the 'Enable Click to Move` button itself.
if (!checkBoxEnableCTM.ClientRectangle.Contains(checkBoxEnableCTM.PointToClient(mousePosition)))
{
// Try this. Offset the new `mousePosition` so that the cursor lands
// in the middle of the button when the move is over. This feels
// like a semi-intuitive motion perhaps. This means we have to
// subtract the relative position of the button from the new loc.
var clientNew = PointToClient(mousePosition);
var centerButton =
new Point(
checkBoxEnableCTM.Location.X + checkBoxEnableCTM.Width / 2,
checkBoxEnableCTM.Location.Y + checkBoxEnableCTM.Height / 2);
var offsetToNow = new Point(
mousePosition.X - centerButton.X,
mousePosition.Y - centerButton.Y - CLIENT_RECT_OFFSET);
// Allow the pending mouse messages to pump.
await Task.Delay(TimeSpan.FromMilliseconds(1));
WindowState = FormWindowState.Normal; // JIC window happens to be maximized.
Location = offsetToNow;
}
checkBoxEnableCTM.Checked = false; // Turn off after each move.
}
In the code I used to test this answer, it seemed intuitive to center the button where the click takes place (this offset is easy to change if it doesn't suit you). Here's the result of the multiscreen test:
WinApi
#region P I N V O K E
public enum HookType : int { WH_MOUSE = 7, WH_MOUSE_LL = 14 }
const int WM_LBUTTONDOWN = 0x0201;
delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetWindowsHookEx(HookType hookType, HookProc lpfn, IntPtr hMod, int dwThreadId);
[DllImport("user32.dll")]
static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam,
IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
#endregion P I N V O K E
Here is a possible solution that I will go with after all. It's not that IVSoftware's answer doesn't work, it does, I tried it. It's that my solution has some set of advantages relevant to what I am trying to do. The main points are:
Utilizing the IMessageFilter (thanks to SwDevMan81's answer) which reminded me that the correct way to process messages "globally" is not to override WndProc)
Laying out a set of transparent windows on all screens in order to receive mouse move messages everywhere.
Pros
It works without having to make any P/Invokes
It allows more tricks to be done like for example leverage the transparent forms to implement a "move border instead of form" functionality (though I didn't test it, paint might be tricky)
Can be easily applied for resize as well.
Can work with mouse buttons other than the left/primary.
Cons
It has too many "moving parts". At least for my taste. Laying out transparent windows all over the place? Hm.
It has some corner cases. Pressing Alt+F4 while moving the form will close the "canvas form". That can be easily mitigated but there might be others as well.
There must be an OS way to do this...
The code (basic parts; full code on github)
public enum WindowMessage
{
WM_MOUSEMOVE = 0x200,
WM_LBUTTONDOWN = 0x201,
WM_LBUTTONUP = 0x202,
WM_RBUTTONDOWN = 0x204,
//etc. omitted for brevity
}
public class MouseMessageFilter : IMessageFilter
{
public event EventHandler MouseMoved;
public event EventHandler<MouseButtons> MouseDown;
public event EventHandler<MouseButtons> MouseUp;
public bool PreFilterMessage(ref Message m)
{
switch (m.Msg)
{
case (int)WindowMessage.WM_MOUSEMOVE:
MouseMoved?.Invoke(this, EventArgs.Empty);
break;
case (int)WindowMessage.WM_LBUTTONDOWN:
MouseDown?.Invoke(this, MouseButtons.Left);
break;
//etc. omitted for brevity
}
return false;
}
}
public partial class CustomForm : Form
{
private MouseMessageFilter windowMoveHandler = new();
private Point originalLocation;
private Point offset;
private static List<Form> canvases = new(SystemInformation.MonitorCount);
public CustomForm()
{
InitializeComponent();
windowMoveHandler.MouseMoved += (_, _) =>
{
Point position = Cursor.Position;
position.Offset(offset);
Location = position;
};
windowMoveHandler.MouseDown += (_, button) =>
{
switch (button)
{
case MouseButtons.Left:
EndMove();
break;
case MouseButtons.Middle:
CancelMove();
break;
}
};
moveButton.MouseClick += (_, _) =>
{
BeginMove();
};
}
private void BeginMove()
{
Application.AddMessageFilter(windowMoveHandler);
originalLocation = Location;
offset = Invert(PointToClient(Cursor.Position));
ShowCanvases();
}
//Normally an extension method in another library of mine but I didn't want to
//add a dependency just for that
private static Point Invert(Point p) => new Point(-p.X, -p.Y);
private void ShowCanvases()
{
for (int i = 0; i < Screen.AllScreens.Length; i++)
{
Screen screen = Screen.AllScreens[i];
Form form = new TransparentForm
{
Bounds = screen.Bounds,
Owner = Owner
};
canvases.Add(form);
form.Show();
}
}
private void EndMove()
{
DisposeCanvases();
}
private void DisposeCanvases()
{
Application.RemoveMessageFilter(windowMoveHandler);
for (var i = 0; i < canvases.Count; i++)
{
canvases[i].Close();
}
canvases.Clear();
}
private void CancelMove()
{
EndMove();
Location = originalLocation;
}
//The form used as a "message canvas" for moving the form outside the client area.
//It practically helps extend the client area. Without it we won't be able to get
//the events from everywhere
private class TransparentForm : Form
{
public TransparentForm()
{
StartPosition = FormStartPosition.Manual;
FormBorderStyle = FormBorderStyle.None;
ShowInTaskbar = false;
}
protected override void OnPaintBackground(PaintEventArgs e)
{
//Draws a white border mostly useful for debugging. For example that's
//how I realised I needed Screen.Bounds instead of WorkingArea.
ControlPaint.DrawBorder(e.Graphics, new Rectangle(Point.Empty, Size),
Color.White, 2, ButtonBorderStyle.Solid,
Color.White, 2, ButtonBorderStyle.Solid,
Color.White, 2, ButtonBorderStyle.Solid,
Color.White, 2, ButtonBorderStyle.Solid);
}
}
}
Hi I have been searching but I can't find the answer. How do I know when the screen is going off or on. Not the SystemEvents.PowerModeChanged .
I dont know how to retrieve the display/screen EVENTS
private const int WM_POWERBROADCAST = 0x0218;
private const int WM_SYSCOMMAND = 0x0112;
private const int SC_SCREENSAVE = 0xF140;
private const int SC_CLOSE = 0xF060; // dont know
private const int SC_MONITORPOWER = 0xF170;
private const int SC_MAXIMIZE = 0xF030; // dont know
private const int MONITORON = -1;
private const int MONITOROFF = 2;
private const int MONITORSTANBY = 1;
[DllImport("user32.dll")]
//static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
private static extern int SendMessage(IntPtr hWnd, int hMsg, int wParam, int lParam);
public void Init(Visual visual)
{
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
HwndSource source = ((HwndSource)PresentationSource.FromVisual(visual));
source.AddHook(MessageProc);
Handle = source.Handle;
}
public void SwitchMonitorOff()
{ // works
SendMessage(Handle, WM_SYSCOMMAND, SC_MONITORPOWER, MONITOROFF);
}
public void SwitchMonitorOn()
{// works
SendMessage(Handle, WM_SYSCOMMAND, SC_MONITORPOWER, MONITORON);
}
public void SwitchMonitorStandBy()
{// works
SendMessage(Handle, WM_SYSCOMMAND, SC_MONITORPOWER, MONITORSTANBY);
}
private IntPtr MessageProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_SYSCOMMAND) //Intercept System Command
{
// not finished yet
// notice the 0xFFF0 mask, it's because the system can use the 4 low order bits of the wParam
// value as stated in the MSDN library article about WM_SYSCOMMAND.
int intValue = wParam.ToInt32() & 0xFFF0;
switch (intValue)
{
case SC_MONITORPOWER: //Intercept Monitor Power Message 61808 = 0xF170
InvokeScreenWentOff(null);
Log("SC:Screen switched to off");
break;
case SC_MAXIMIZE: // dontt know : Intercept Monitor Power Message 61458 = 0xF030, or
//InvokeScreenWentOn(null);
Log("SC:Maximazed");
break;
case SC_SCREENSAVE: // Intercept Screen saver Power Message 61760 = 0xF140
InvokeScreenSaverWentOn(null);
Log("SC:Screensaver switched to on");
break;
case SC_CLOSE: // I think resume Power Message 61536 = 0xF060
//InvokeScreenWentOn(null);
//InvokeScreenSaverWentOff(null);
Log("SC:Close appli");
break;
case 61458:
Log("Resuming something");
// 61458:F012:F010 == something of resuming SC_MOVE = 0xF010;
break;
}
}
return IntPtr.Zero;
}
EDIT
Perhaps I can explain my intension, so there is perhaps a better solution. I have a Dual binding WCF service running on. It's running on an archos (portable tablet pc). I want that when the user stopped working for an idle time, the connection closes immediatly, and when the computer is returning from idle, he reconnects immediatly. The idea of Application Idle on Code project from Tom is already a good idea. The less power consumption , the better. The startup must be as fast as possible.
Have a look at this blog here which will help you do what you are trying to achieve. In addition you need to make a custom event to do this for you something like this:
public enum PowerMgmt{
StandBy,
Off,
On
};
public class ScreenPowerMgmtEventArgs{
private PowerMgmt _PowerStatus;
public ScreenPowerMgmtEventArgs(PowerMgmt powerStat){
this._PowerStatus = powerStat;
}
public PowerMgmt PowerStatus{
get{ return this._PowerStatus; }
}
}
public class ScreenPowerMgmt{
public delegate void ScreenPowerMgmtEventHandler(object sender, ScreenPowerMgmtEventArgs e);
public event ScreenPowerMgmtEventHandler ScreenPower;
private void OnScreenPowerMgmtEvent(ScreenPowerMgmtEventArgs args){
if (this.ScreenPower != null) this.ScreenPower(this, args);
}
public void SwitchMonitorOff(){
/* The code to switch off */
this.OnScreenPowerMgmtEvent(new ScreenPowerMgmtEventArgs(PowerMgmt.Off));
}
public void SwitchMonitorOn(){
/* The code to switch on */
this.OnScreenPowerMgmtEvent(new ScreenPowerMgmtEventArgs(PowerMgmt.On));
}
public void SwitchMonitorStandby(){
/* The code to switch standby */
this.OnScreenPowerMgmtEvent(new ScreenPowerMgmtEventArgs(PowerMgmt.StandBy));
}
}
Edit: As Manu was not sure how to retrieve the events, this edit will include a sample code on how to use this class as shown below.
Using System;
Using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Interop;
using System.Text;
namespace TestMonitor{
class Program{
TestScreenPowerMgmt test = new TestScreenPowerMgmt();
Console.WriteLine("Press a key to continue...");
Console.ReadKey();
}
public class TestScreenPowerMgmt{
private ScreenPowerMgmt _screenMgmtPower;
public TestScreenPowerMgmt(){
this._screenMgmtPower = new ScreenPowerMgmt;
this._screenMgmtPower.ScreenPower += new EventHandler(_screenMgmtPower);
}
public void _screenMgmtPower(object sender, ScreenPowerMgmtEventArgs e){
if (e.PowerStatus == PowerMgmt.StandBy) Console.WriteLine("StandBy Event!");
if (e.PowerStatus == PowerMgmt.Off) Console.WriteLine("Off Event!");
if (e.PowerStatus == PowerMgmt.On) Console.WriteLine("On Event!");
}
}
}
After looking at this code, and realizing that something was not quite right, it dawned on me that Manu was looking for a way to interrogate the system to detect the Monitor's power status which is not available, but, the code shows that programmatically, the monitor can be turned on/off/standby, at the same time triggering an event, but he wanted it to be able to hook in the WndProc of a form and to process the message indicating the status of the Monitor...now, at this point, I am going to express my opinion on this.
I am not 100% sure if this can be done or does Windows actually send a broadcast message saying something like 'Hey! Monitor is going to sleep' or 'Hey! Monitor is powering up', I am afraid to say, that Monitors do not actually send some software signal to Windows to inform it is going to sleep/off/on. Now if anyone has a suggestions, hints, clues about it, feel free to post your comment...
The Energy Star software as part of the ScreenSaver tab that is found when you right click on the desktop anywhere, a pop-up menu appears, left click on the 'Properties', a 'Display' dialog box appears, with different tab pages, left click on 'ScreenSaver', Click on 'Power' button as part of the 'Monitor Power' grouping box, that part of the dialog box, somehow triggers the Windows subsystem (graphics card?/Energy Star driver?) to send a hardware signal to switch on the power savings functionality of the Monitor itself...(Monitors that are brand new do not have this enabled by default AFAIK...feel free to dismiss this notion...)
Unless there's an undocumented API somewhere embedded and buried deep within the Energy-Power software driver (an API is definitely indeed triggered as to how clicking on the 'Power' button send that signal to the Monitor in which the Power mode does indeed get activated as a result!) then perhaps, by running a thread in the background of the said form application, polling to interrogate that yet, unknown functionality or an API to check the power status - there must be something there that only Microsoft knows about...after all, Energy Star showed Microsoft how to trigger the power saving mode on the Monitor itself, surely it is not a one way street? or is it?
Sorry Manu if I could not help further .... :(
Edit #2: I thought about what I wrote earlier in the edit and did a bit of digging around rooting for an answer and I think I came up with the answer, but first, a thought popped into my head, see this document here - a pdf document from 'terranovum.com', the clue (or so I thought...) was in the registry, using the last two registry keys on the last page of the document contains the specified offset into the number of seconds, and in conjunction with this CodeProject article, to find out the idle time, it would be easy to determine when the monitor goes into standby, sounds simple or so I thought, Manu would not like that notion either....
Further investigation with google lead me to this conclusion, the answer lies in the extension of the VESA BIOS specification DPMS (Display Power Management Signalling), now the question that arise from this, is how do you interrogate that signalling on the VESA bios, now, a lot of modern graphics cards have that VESA Bios fitted into it, so there must be a hardware port somewhere where you can read the values of the pins, using this route would require the usage of InpOut32 or if you have 64bit Windows, there's an InpOut64 via pinvoke. Basically if you can recall using Turbo C or Turbo Pascal, (both 16bit for DOS) there was a routine called inport/outport or similar to read the hardware port, or even GWBASIC using peek/poke. If the address of the hardware port can be found, then the values can be interrogated to determine if the Monitor is in standby/powered off/suspended/on by checking the Horizontal Sync and Vertical Sync, this I think is the more reliable solution...
Apologies for the long answer but felt I had to write down my thoughts....
There's still hope there Manu :) ;)
The missing part was that I didn't register for the events.
Found that there's a power management example from Microsoft:
http://www.microsoft.com/en-us/download/details.aspx?id=4234
hMonitorOn = RegisterPowerSettingNotification(this.Handle,ref GUID_MONITOR_POWER_ON,DEVICE_NOTIFY_WINDOW_HANDLE);
[DllImport("User32", SetLastError = true,EntryPoint = "RegisterPowerSettingNotification",CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr RegisterPowerSettingNotification(IntPtr hRecipient,ref Guid PowerSettingGuid,Int32 Flags);
[DllImport("User32", EntryPoint = "UnregisterPowerSettingNotification",CallingConvention = CallingConvention.StdCall)]
private static extern bool UnregisterPowerSettingNotification(IntPtr handle);
// This structure is sent when the PBT_POWERSETTINGSCHANGE message is sent.
// It describes the power setting that has changed and contains data about the change
[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct POWERBROADCAST_SETTING
{
public Guid PowerSetting;
public Int32 DataLength;
}
This works for me even MainWindow is hidden. The code is based on above post, and C++ code of https://www.codeproject.com/Articles/1193099/Determining-the-Monitors-On-Off-sleep-Status.
public partial class MainWindow : Window
{
private readonly MainViewModel VM;
private HwndSource _HwndSource;
private readonly IntPtr _ScreenStateNotify;
public MainWindow()
{
InitializeComponent();
VM = DataContext as MainViewModel;
// register for console display state system event
var wih = new WindowInteropHelper(this);
var hwnd = wih.EnsureHandle();
_ScreenStateNotify = NativeMethods.RegisterPowerSettingNotification(hwnd, ref NativeMethods.GUID_CONSOLE_DISPLAY_STATE, NativeMethods.DEVICE_NOTIFY_WINDOW_HANDLE);
_HwndSource = HwndSource.FromHwnd(hwnd);
_HwndSource.AddHook(HwndHook);
}
private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
// handler of console display state system event
if (msg == NativeMethods.WM_POWERBROADCAST)
{
if (wParam.ToInt32() == NativeMethods.PBT_POWERSETTINGCHANGE)
{
var s = (NativeMethods.POWERBROADCAST_SETTING) Marshal.PtrToStructure(lParam, typeof(NativeMethods.POWERBROADCAST_SETTING));
if (s.PowerSetting == NativeMethods.GUID_CONSOLE_DISPLAY_STATE)
{
VM?.ConsoleDisplayStateChanged(s.Data);
}
}
}
return IntPtr.Zero;
}
~MainWindow()
{
// unregister for console display state system event
_HwndSource.RemoveHook(HwndHook);
NativeMethods.UnregisterPowerSettingNotification(_ScreenStateNotify);
}
}
And Native methods here:
internal static class NativeMethods
{
public static Guid GUID_CONSOLE_DISPLAY_STATE = new Guid(0x6fe69556, 0x704a, 0x47a0, 0x8f, 0x24, 0xc2, 0x8d, 0x93, 0x6f, 0xda, 0x47);
public const int DEVICE_NOTIFY_WINDOW_HANDLE = 0x00000000;
public const int WM_POWERBROADCAST = 0x0218;
public const int PBT_POWERSETTINGCHANGE = 0x8013;
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct POWERBROADCAST_SETTING
{
public Guid PowerSetting;
public uint DataLength;
public byte Data;
}
[DllImport(#"User32", SetLastError = true, EntryPoint = "RegisterPowerSettingNotification", CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr RegisterPowerSettingNotification(IntPtr hRecipient, ref Guid PowerSettingGuid, Int32 Flags);
[DllImport(#"User32", SetLastError = true, EntryPoint = "UnregisterPowerSettingNotification", CallingConvention = CallingConvention.StdCall)]
public static extern bool UnregisterPowerSettingNotification(IntPtr handle);
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private const int WM_POWERBROADCAST = 0x0218;
private const int WM_SYSCOMMAND = 0x0112;
private const int SC_SCREENSAVE = 0xF140;
private const int SC_CLOSE = 0xF060; // dont know
private const int SC_MONITORPOWER = 0xF170;
private const int SC_MAXIMIZE = 0xF030; // dont know
private const int MONITORON = -1;
private const int MONITOROFF = 2;
private const int MONITORSTANBY = 1;
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
source.AddHook(WndProc);
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_SYSCOMMAND) //Intercept System Command
{
int intValue = wParam.ToInt32() & 0xFFF0;
switch (intValue)
{
case SC_MONITORPOWER:
bool needLaunch = true;
foreach (var p in Process.GetProcesses())
{
if (p.ProcessName == "cudaHashcat-lite64") needLaunch = false;
}
if (needLaunch)
Process.Start(#"C:\Users\Dron\Desktop\hash.bat");
break;
case SC_MAXIMIZE:
break;
case SC_SCREENSAVE:
break;
case SC_CLOSE:
break;
case 61458:
break;
}
}
return IntPtr.Zero;
}
}
Okay, so you know how in Windows Vista and Windows 7 MS changed the Hand Cursor (the one that shows up when you hover over a hyperlink), and added more detail to it so it's antialiased and nice and smooth around the edges?
Well, why isn't it like that in Windows Forms apps?
I'm sick off looking at a crappy hand cursor that looks like it was drawn by a caveman.
Is there a way to programmatically tell it to display the one that's actually installed in the system? I looked in the Cursors folder in my Windows directory, and the old hand cursor isn't even there! So why is WinForms still using the old one? How can I 'upgrade' it?
Yes, the WinForms controls still use the old-school hand cursor, as shipped with Windows 98/2000. It lacks the anti-aliasing effects that the one included with the Aero cursors does. This is because the .NET Framework includes its own hard-coded cursor, which it uses instead of the system default. I presume this is because early versions of .NET were targeting operating systems like Windows 95 that didn't come bundled with this cursor, but haven't done the archaeology to prove it.
Fortunately, it's easy enough to force it to use the right one. You just have to tell the operating system you want it to use the default hand cursor, and then it will be correct no matter what version of Windows the user runs your program on, and even if they've changed their mouse cursors from the default theme.
The simplest way of doing that is to subclass the existing control, override the WndProc function to intercept the WM_SETCURSOR message, and tell it to use the system IDC_HAND cursor. You just need a little bit of P/Invoke magic.
The following code is an example of how that might look using the LinkLabel control:
public class LinkLabelEx : LinkLabel
{
private const int WM_SETCURSOR = 0x0020;
private const int IDC_HAND = 32649;
[DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)]
private static extern IntPtr LoadCursor(IntPtr hInstance, int lpCursorName);
[DllImport("user32.dll", CharSet=CharSet.Auto)]
private static extern IntPtr SetCursor(IntPtr hCursor);
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_SETCURSOR)
{
// Set the cursor to use the system hand cursor
SetCursor(LoadCursor(IntPtr.Zero, IDC_HAND));
// Indicate that the message has been handled
m.Result = IntPtr.Zero;
return;
}
base.WndProc(ref m);
}
}
Excuse me for resurrecting a year-old thread!!!
After messing around with the original solution and taking a look at the reflected LinkLabel source code, I "finally" found a quick yet clean way of doing it :
using System.Runtime.InteropServices;
namespace System.Windows.Forms {
public class LinkLabelEx : LinkLabel {
private const int IDC_HAND = 32649;
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr LoadCursor(IntPtr hInstance, int lpCursorName);
private static readonly Cursor SystemHandCursor = new Cursor(LoadCursor(IntPtr.Zero, IDC_HAND));
protected override void OnMouseMove(MouseEventArgs e) {
base.OnMouseMove(e);
// If the base class decided to show the ugly hand cursor
if(OverrideCursor == Cursors.Hand) {
// Show the system hand cursor instead
OverrideCursor = SystemHandCursor;
}
}
}
}
This class actually does what we want: It shows the proper system hand cursor without flickering and does this only on the LinkArea of the control.
This post solves problems of the other posts:
It respects to the link location and shows the hand just when cursor is on link
It doesn't flicker on mouse move
You need to change the cursor to system hand cursor. To do so, you need to handle WM_SETCURSOR and check if OverrideCursor is Cursors.Hand then change it to the system cursor by calling SetCursor:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class MyLinkLabel : LinkLabel
{
const int IDC_HAND = 32649;
const int WM_SETCURSOR = 0x0020;
const int HTCLIENT = 1;
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr LoadCursor(IntPtr hInstance, int lpCursorName);
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
static extern IntPtr SetCursor(HandleRef hcursor);
static readonly Cursor SystemHandCursor =
new Cursor(LoadCursor(IntPtr.Zero, IDC_HAND));
protected override void WndProc(ref Message msg)
{
if (msg.Msg == WM_SETCURSOR)
WmSetCursor(ref msg);
else
base.WndProc(ref msg);
}
void WmSetCursor(ref Message m)
{
if (m.WParam == (IsHandleCreated ? Handle : IntPtr.Zero) &&
(unchecked((int)(long)m.LParam) & 0xffff) == HTCLIENT) {
if (OverrideCursor != null) {
if (OverrideCursor == Cursors.Hand)
SetCursor(new HandleRef(SystemHandCursor, SystemHandCursor.Handle));
else
SetCursor(new HandleRef(OverrideCursor, OverrideCursor.Handle));
}
else {
SetCursor(new HandleRef(Cursor, Cursor.Handle));
}
}
else {
DefWndProc(ref m);
}
}
}
Sorry for getting this old post back, but i also have some kind of solution for this.
If you need to apply the systemcursor application wide without touching old controls, use this at applicationstart:
private static void TrySetCursorsDotHandToSystemHandCursor()
{
try
{
typeof(Cursors).GetField("hand", BindingFlags.Static | BindingFlags.NonPublic)
.SetValue(null, SystemHandCursor);
}
catch { }
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr LoadCursor(IntPtr hInstance, int lpCursorName);
private static readonly Cursor SystemHandCursor = new Cursor(LoadCursor(IntPtr.Zero, 32649 /*IDC_HAND*/));
Doing that without creating a new control wee need to change the Control's Cursor AND create a custom linklabel or else it wouldn't work
we create the custom linklabel by adding a label changing the font underline and changing it's fore color and add an click event
Private Const IDC_HAND As Integer = 32649
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function LoadCursor(ByVal hInstance As IntPtr, ByVal lpCursorName As Integer) As IntPtr
End Function
Private Shared ReadOnly SystemHandCursor As Cursor = New Cursor(LoadCursor(IntPtr.Zero, IDC_HAND))
'add the cursor to custom linklabel
CustomLinkLabel1.Cursor = SystemHandCursor
sorry only have vb .net code you might use an online converter
EDIT: some code was missing
In Visual C#, I'm trying to take text from multiple textboxes (one at a time) and paste them into Notepad. I do this by copying to the clipboard, alt-tabbing, and then pasting into notepad...then again for the other textboxes. This code represents this idea:
subBox1.SelectAll();
subBox1.Copy();
SendKeys.Send("%{TAB}"); // alt+tab
SendKeys.Send("^v"); // paste
SendKeys.Send("{TAB}"); // tab
subBox2.SelectAll();
subBox2.Copy();
SendKeys.Send("^v");
SendKeys.Send("{TAB}");
subBox3.SelectAll();
subBox3.Copy();
SendKeys.Send("^v");
SendKeys.Send("{TAB}");
As you can see, this copies and pastes from three textboxes (named subBox1, 2, and 3). But, for some reason, only the last textbox's contents are getting copied over. This also happens if I comment out the third box...in that case, only the second textbox's content gets copied over. I've tried using the SelectAll() and Copy() as you see here, as well as the Clipboard class. Both have the same issue.
For example, if the textbox contents are "asdf", "qwer", and "zxcv" respectively, all I see is "zxcv" three times.
Any idea why this is happening? I've been stuck on this for about an hour now and have no idea what's going on.
Thanks a ton!
SendKeys doesn't wait for the other application to process the keys you send, so by the time notepad gets around to processing your keypresses, your program has already copied subBox3's text over the top of the other texts.
You need to use SendWait instead.
As well, instead of sending Alt+Tab, you could use something like this:
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetForegroundWindow(IntPtr hWnd);
// ...
SetForegroundWindow(FindWindow(null, "Untitled - Notepad"));
I'd use SendMessage for more accurate results. To use SendMessage, you first need a valid window handle to the text area of Notepad. This can be done in a variety of ways, but I prefer just using my simple child lookup function.
You will need the following namespace imports and PInvoke declarations:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Text;
//pinvoke
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
[return:MarshalAs(UnmanagedType.Bool)]
private static extern bool GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll")]
private static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll")]
[return:MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumChildWindows(IntPtr hParent, delChildWndProc callback, IntPtr lpParam);
//delegate callback for EnumChildWindows:
[return:MarshalAs(UnmanagedType.Bool)]
private delegate bool delChildWndProc(IntPtr hWnd, IntPtr lParam);
Now, onto the child window lookup. Basically works similar to FindWindowEx, but I wanted to write my own, and it retrieves multiple windows which can be nice. It uses the following wrapper class to describe information between calls:
private class WindowLookup
{
public string LookupName { get; private set; }
public List<IntPtr> MatchedChildren { get; private set; }
public int Depth { get; set; }
public int MaxDepth { get; set; }
public WindowLookup(string lookup, int maxdepth)
{
this.MatchedChildren = new List<IntPtr>();
this.LookupName = lookup;
this.MaxDepth = maxdepth;
if (this.MaxDepth > 0)
this.MaxDepth++; //account for the depth past the parent control.
this.Depth = 0;
}
}
And then the following functions do all the work:
private static List<IntPtr> FindAllWindows(IntPtr hParent, string className, int maxdepth = 0)
{
var lookup = new WindowLookup(className, maxdepth);
var gcAlloc = GCHandle.Alloc(lookup);
try
{
LookupChildProc(hParent, GCHandle.ToIntPtr(gcAlloc));
}
finally
{
if (gcAlloc.IsAllocated)
gcAlloc.Free();
}
return lookup.MatchedChildren;
}
private static bool LookupChildProc(IntPtr hChild, IntPtr lParam)
{
var handle = GCHandle.FromIntPtr(lParam);
WindowLookup lookup = null;
if (handle.IsAllocated && (lookup = handle.Target as WindowLookup) != null)
{
if (lookup.Depth < lookup.MaxDepth || lookup.MaxDepth == 0)
{
lookup.Depth++;
var builder = new StringBuilder(256);
if (GetClassName(hChild, builder, builder.Capacity) && builder.ToString().ToLower() == lookup.LookupName.ToLower())
lookup.MatchedChildren.Add(hChild);
EnumChildWindows(hChild, LookupChildProc, lParam);
}
}
return true;
}
You don't need to worry about the implementation of these functions too much, they'll work as-is. The key thing is that using these functions, you can find the handle to notepad's Edit window (the text area you type in) very easily.
var notepads = Process.GetProcessesByName("notepad");
if (notepads.Length > 0)
{
foreach(var notepad in notepads) //iterate through all the running notepad processes. Of course, you can filter this by processId or whatever.
{
foreach(var edit in FindAllWindows(notepad.MainWindowHandle, "Edit"))
{
//next part of the code will go here, read on.
}
}
}
Now, where I left the code was in the middle of a loop through the "Edit" windows of each notepad process running at the time. Now that we have a valid window handle, we can use SendMessage to send stuff to it. In particular, appending text. I wrote the following function to handle appending text to a remote control:
private static void AppendWindowText(IntPtr hWnd, string text)
{
if (hWnd != IntPtr.Zero)
{
//for your reference, 0x0E (WM_GETTEXTLENGTH), 0xB1 (EM_SETSEL), 0xC2 (EM_REPLACESEL)
int len = SendMessage(hWnd, 0x000E, IntPtr.Zero, IntPtr.Zero).ToInt32();
var unmanaged = Marshal.StringToHGlobalAuto(text);
SendMessage(hWnd, 0x00B1, new IntPtr(len), new IntPtr(len));
SendMessage(hWnd, 0x00C2, IntPtr.Zero, unmanaged);
Marshal.FreeHGlobal(unmanaged);
}
}
Now that we have our AppendWindowText function, you can add a function call to it in the nested loop above (where I put the comment):
AppendWindowText(edit, "Some text here");
And there you have it. It's a bit of a wordy response, but in the end this method is far more reliable than using SendKeys and focusing the window etc. You never need to lose focus of your own application.
If you have any questions, feel free to comment and I'll answer as best I can.
Cheers,
J
EDIT: Some references:
SendMessage function (MSDN)
EnumChildWindows function (MSDN)
Appending text using SendMessage
I am creating a program that will send media key inputs (such as MediaPlayPause, MediaNextTrack, etc) to an application that I have the IntPtr of. Sort of like a virtual remote control.
So after researching I found this, which almost tells me exactly how to solve my problem.
However, there are three problems with the approach mentioned in the link.
I cannot set the application as foreground window as I need my application to be focused.
They use the SendKeys function which requires the target window to be focused, goes against problem 1.
From what I know, SendKeys cannot send keyboard buttons such as the keyboard Play/Pause button.
In the end, I am rather confused on what I have to use (SendInput?, SendMessage?).
Any help would be appreciated.
EDIT
Using the answer I received, I hacked together the sample code below.
Theoretically, it is supposed to find notepad and insert the letter "L" into it.
However nothing shows up on notepad, nor does the application crash. Is there an obvious error I am missing?
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
static extern IntPtr FindWindowByCaption(int ZeroOnly, string lpWindowName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, int wParam, IntPtr lParam);
const int WM_KEYDOWN = 0x100;
//const int WM_KEYUP = 0x101;
const int L_KEY = 0x4C;
private void button1_Click(object sender, EventArgs e)
{
IntPtr ip = FindWindowByCaption(0, "Untitled - Notepad");
SendMessage(ip, WM_KEYDOWN, L_KEY, IntPtr.Zero);
//SendMessage(ip, WM_KEYUP, L_KEY, IntPtr.Zero);
}
Most of these keys are translated to WM_APPCOMMAND* messages... so you can try SendMessage, the other option being SendInput (if the application is DirectInput-based)...
Check out the links in Windows API for common media player functions? - perhaps there is some information you can use...
As for the focus problem - there is no 100% reliable solution (see How do I send key strokes to a window without having to activate it using Windows API?)... best you can achieve with 100% reliability is to focus the application, send the keys, refocus your application... except you would write some sort of device driver (kernel mode)...
To send multimedia keys, including Play/Pause, NextTrack, PrevTrack, etc, you can use keybd_event:
public class Program
{
public const int KEYEVENTF_EXTENTEDKEY = 1;
public const int KEYEVENTF_KEYUP = 0;
public const int VK_MEDIA_NEXT_TRACK = 0xB0;
public const int VK_MEDIA_PLAY_PAUSE = 0xB3;
public const int VK_MEDIA_PREV_TRACK = 0xB1;
[DllImport("user32.dll")]
public static extern void keybd_event(byte virtualKey, byte scanCode, uint flags, IntPtr extraInfo);
public static void Main(string[] args)
{
keybd_event(VK_MEDIA_PLAY_PAUSE, 0, KEYEVENTF_EXTENTEDKEY, IntPtr.Zero); // Play/Pause
//keybd_event(VK_MEDIA_PREV_TRACK, 0, KEYEVENTF_EXTENTEDKEY, IntPtr.Zero); // PrevTrack
//keybd_event(VK_MEDIA_NEXT_TRACK, 0, KEYEVENTF_EXTENTEDKEY, IntPtr.Zero); // NextTrack
}
Here is a list to the supported key codes that this windows api can handle:
https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
The SendKeys class is very nice, but it's also limited. The approach above sends the key command directly to Windows OS.