Question:
I’m trying to do some trick with Powershell. I want to write a script and that script can listen to the mouse events (click, move, etc) inside the powershell console.
For example, while my script is active, when I click the mouse inside the powershell console, the console can output the position of my cursor.
Is it possible? If possible, how?
Thanks in advance.
Answer:
I’ve found that it’s possible to do this using the following:
1 2 |
[System.Windows.Forms.UserControl]::MouseButtons |
Which returns a string of the currently pressed mouse buttons. We poll this and [System.Windows.Forms.Cursor]::Position
as per WorWin to keep track of where the mouse is and what buttons are pressed.
Keeping track of where the mouse is inside of the console window is a bit tricky, though. I personally use a custom class for this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
Add-Type -ReferencedAssemblies System.Drawing @" using System; using System.Drawing; using System.Runtime.InteropServices; public class Window { [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); public RECT bounds; public bool isForeground; private IntPtr hWnd; public int Width; public int Height; public Window(IntPtr handle) { hWnd = handle; Update(); } public void Update() { if(GetWindowRect(hWnd, out bounds)) { Width = bounds.Right - bounds.Left; Height = bounds.Bottom - bounds.Top; if(GetForegroundWindow() == hWnd){isForeground = true;} else{isForeground = false;} } } public bool Contains(Point pt) { return bounds.Contains(pt); } } public struct RECT { public int Left; public int Top; public int Right; public int Bottom; public Boolean Contains(Point pt) { if( (pt.X >= Left) && (pt.X < Right) && (pt.Y >= Top) && (pt.Y < Bottom) ){ return true;} return false; } } public struct POINT { public int X; public int Y; public static implicit operator Point(POINT point) { return new Point(point.X, point.Y); } } "@ |
To get the Powershell console window:
1 2 3 |
$ourID = (get-process -id (Get-WmiObject -class win32_process -Filter ("ProcessID = $pid")).parentprocessid).mainwindowhandle; $win = New-Object Window($ourID); |
Now, $win.bounds
gives us the outer bounds of the console window, so if we want to find which buffercell the cursor is in we’re going to have to do some work on our bounds.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$innerOffsets = new-object RECT; $innerOffsets.Top = [Windows.Forms.SystemInformation]::CaptionHeight + [Windows.Forms.SystemInformation]::HorizontalResizeBorderThickness + [Windows.Forms.SystemInformation]::Border3DSize.Height; $innerOffsets.Bottom = -([Windows.Forms.SystemInformation]::HorizontalResizeBorderThickness + [Windows.Forms.SystemInformation]::Border3DSize.Height); $inneroffsets.Left = [Windows.Forms.SystemInformation]::VerticalResizeBorderThickness + [Windows.Forms.SystemInformation]::Border3DSize.Width; $inneroffsets.Right = -([Windows.Forms.SystemInformation]::VerticalResizeBorderThickness + [Windows.Forms.SystemInformation]::Border3DSize.Width); if([console]::bufferheight -gt [console]::windowheight) { $inneroffsets.right -= [Windows.Forms.SystemInformation]::HorizontalScrollBarThumbWidth; } if([console]::bufferwidth -gt [console]::windowwidth) { $inneroffsets.bottom -= [Windows.Forms.SystemInformation]::VerticalScrollBarThumbHeight; } |
This gives us the offsets we need to get the inner bounds of the window. From here we can get our relative location on the window.
1 2 3 4 |
$mp = [Windows.Forms.Cursor]::Position; $mp.x -= ($win.bounds.left + $inneroffsets.left); $mp.y -= ($win.bounds.top + $inneroffsets.top); |
Now we can convert our mouse position to buffer cell position.
1 2 3 4 5 |
$innerWidth = ($win.bounds.right + $inneroffsets.right) - ($win.bounds.left + $inneroffsets.left); $innerheight = ($win.bounds.bottom + $inneroffsets.bottom) - ($win.bounds.top + $inneroffsets.top); $mp.x = [Math]::Floor($mp.x / ( $innerwidth / [console]::windowWidth)); $mp.y = [Math]::Floor($mp.y / ( $innerheight / [console]::windowheight)); |
You’ll have to use $win.update()
every time you check. You can also use $win.isforeground
and [Windows.Forms.UserControl]::MouseButtons -match "Left"
to only check when the console window is clicked as well.