Solution
The main idea is registering a global mouse hook and handling desired mouse event and run a visual studio command. To do so:
- Start by creating a Visual Studio Package project.
- Register a global mouse hook using
SetWindowsHookEx
by passing WH_MOUSE_LL
and handle desired mouse event, for example WM_XBUTTONDOWN
. Perform registration when the solution loads.
Run desired Visual Studio Command using DTE.ExecuteCommand
passing suitable command, for example Build.BuildSolution
:
var dte = (EnvDTE.DTE)this.GetService(typeof(EnvDTE.DTE));
dte.ExecuteCommand("Build.BuildSolution");
- Don't forget to unhook using
UnhookWindowsHookEx
when the solution closed.
Note:
To find the command that you need, go to Tools → Options → Environment → KeyBoard
and find the command which you need.
You will find lots of resources about how to register a global mouse hook like this which I changed a bit and used for test. At the end of the post you can find full source code.
In Visual Studio 2013 Add ins are deprecated, so while you can do the same using a Visual Studio Add-in project but it's better to do it using VSPackages.
Implementaion
Start by creating a Visual Studio Package project and change the code of package to the code which I post here. Also add the classes which I used for global mouse hook and windows API to the solution.
Package
Here is the full code for my Package:
using Microsoft.VisualStudio.Shell;
using System;
using System.Runtime.InteropServices;
[PackageRegistration(UseManagedResourcesOnly = true)]
[InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
[Guid(GuidList.guidVSPackage1PkgString)]
[ProvideAutoLoad(Microsoft.VisualStudio.Shell.Interop.UIContextGuids80.SolutionExists)]
public sealed class VSPackage1Package : Package
{
public VSPackage1Package() { }
EnvDTE.DTE dte;
protected override void Initialize()
{
base.Initialize();
dte = (EnvDTE.DTE)this.GetService(typeof(EnvDTE.DTE));
dte.Events.SolutionEvents.Opened += SolutionEvents_Opened;
dte.Events.SolutionEvents.AfterClosing += SolutionEvents_AfterClosing;
}
void SolutionEvents_AfterClosing() { MouseHook.Stop(); }
void SolutionEvents_Opened()
{
MouseHook.Start();
MouseHook.MouseAction += MouseHook_MouseAction;
}
void MouseHook_MouseAction(object sender, EventArgs e)
{
dte.ExecuteCommand("Build.BuildSolution");
}
}
Windows API messages, structures and methods
using System;
using System.Runtime.InteropServices;
public class Win32
{
public delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);
public const int WH_MOUSE_LL = 14;
public enum MouseMessages
{
WM_LBUTTONDOWN = 0x0201, WM_LBUTTONUP = 0x0202,
WM_MOUSEMOVE = 0x0200, WM_MOUSEWHEEL = 0x020A,
WM_RBUTTONDOWN = 0x0204, WM_RBUTTONUP = 0x0205
}
[StructLayout(LayoutKind.Sequential)]
public struct POINT { public int x; public int y; }
[StructLayout(LayoutKind.Sequential)]
public struct MSLLHOOKSTRUCT
{
public POINT pt;
public uint mouseData;
public uint flags;
public uint time;
public IntPtr dwExtraInfo;
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProc lpfn,
IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam,
IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
}
Global Mouse Hook
Since I don't have XButton in my mouse, I handled WM_RBUTTONDOWN
event.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
public static class MouseHook
{
public static event EventHandler MouseAction = delegate { };
private static Win32.LowLevelMouseProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;
public static void Start() { _hookID = SetHook(_proc); }
public static void Stop() { Win32.UnhookWindowsHookEx(_hookID); }
private static IntPtr SetHook(Win32.LowLevelMouseProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
var handle = Win32.GetModuleHandle(curModule.ModuleName);
return Win32.SetWindowsHookEx(Win32.WH_MOUSE_LL, proc, handle, 0);
}
}
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 &&
Win32.MouseMessages.WM_RBUTTONDOWN == (Win32.MouseMessages)wParam)
{
Win32.MSLLHOOKSTRUCT hookStruct =
(Win32.MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam,
typeof(Win32.MSLLHOOKSTRUCT));
MouseAction(null, new EventArgs());
}
return Win32.CallNextHookEx(_hookID, nCode, wParam, lParam);
}
}
InstalledProductRegistration
attribute ofVSPackage1Package
class and use instead of the attribute which I used. – Bilodeau