我想以编程方式检测用户何时从 Visual Studio 扩展 (VSIX) 中与 Visual Studio 交互。我不需要任何按键或鼠标事件信息,只是通知用户正在安装 VSIX 的正在运行的 Visual Studio 实例中执行某些操作。我寻求一个“信号”来了解用户是否在 VS IDE 上移动鼠标,或单击任何鼠标按钮,或按正在运行的 VS 实例将“看到”的任何键或组合键。
我为什么想知道这个? 我有一个正在运行的不活动计时器,如果不活动达到或超过定义的时间段,它将触发一些行为。当用户在此之前执行任何形式的键盘/鼠标输入时,我需要能够重置计时器的开始时间。这不仅限于代码编辑器窗口,还包括与正在运行的 Visual Studio 实例的任何形式的用户交互。因此,它同样不是 Windows 系统范围的,但它超出了扩展 (VSIX) 本身。
目前,我正在使用系统范围的挂钩,并尝试将从 EnvDTE80 对象获取的事件消息过滤到 Visual Studio 主窗口句柄。这似乎过于密集,导致需要某种评估的过多 Windows 消息,并且会产生周期性减慢整个环境响应速度的副作用。 (当系统速度变慢时,它不会执行垃圾收集)。
这是我正在使用的内容的经过清理/简化的要点:
using System;
using System.Runtime.InteropServices;
public class UserActivityMonitor
{
[DllImport("user32.dll")]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelInputProc callback, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll")]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll")]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
private delegate IntPtr LowLevelInputProc(int nCode, IntPtr wParam, IntPtr lParam);
private const int WH_KEYBOARD_LL = 13;
private const int WH_MOUSE_LL = 14;
private const int WM_KEYDOWN = 0x0100;
private const int WM_LBUTTONDOWN = 0x0201;
private const int WM_RBUTTONDOWN = 0x0204;
private const int WM_MOUSEMOVE = 0x0200;
private static IntPtr keyboardHookID = IntPtr.Zero;
private static IntPtr mouseHookID = IntPtr.Zero;
public void StartMonitoring()
{
// Set up the keyboard hook
keyboardHookID = SetWindowsHookEx(WH_KEYBOARD_LL, HookKeyboardCallback, IntPtr.Zero, 0);
// Set up the mouse hook
mouseHookID = SetWindowsHookEx(WH_MOUSE_LL, HookMouseCallback, IntPtr.Zero, 0);
}
public void StopMonitoring()
{
// Unhook the keyboard hook
UnhookWindowsHookEx(keyboardHookID);
// Unhook the mouse hook
UnhookWindowsHookEx(mouseHookID);
}
private IntPtr HookKeyboardCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
{
// Handle keyboard event - reset the start time to now (not shown)
}
return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
}
private IntPtr HookMouseCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && (wParam == (IntPtr)WM_LBUTTONDOWN || wParam == (IntPtr)WM_RBUTTONDOWN || wParam == (IntPtr)WM_MOUSEMOVE))
{
// Handle mouse move event - reset the start time to now (not shown)
}
return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
}
}
与我的实际专有代码的区别在于,当我在钩子回调中有代码时,我还尝试从
lParam
确定窗口句柄,并将其与使用 EnvDTE80
引用获得的 Visual Studio 主窗口句柄进行比较,因此本质上,我只会重置我的不活动计时器,以便与安装了扩展的正在运行的 Visual Studio 实例进行有效的用户交互。
我想要一种更高级别的方法来实现相同的目的,最好避免 P/Invoke 调用并仅使用托管代码。
我放弃了这种方法,而是根据检测用户活动
简单地询问系统每秒的空闲时间每当计时器结束时,我都会进行一个简单的调用来检索用户不活动时间段的时间跨度(理论上可能为零)。
private TimeSpan GetElapsedIdleTime()
{
LASTINPUTINFO lastInputInfo = new LASTINPUTINFO();
lastInputInfo.cbSize = (uint)System.Runtime.InteropServices.Marshal.SizeOf(lastInputInfo);
GetLastInputInfo(ref lastInputInfo);
long ticksElapsed = ((long)Environment.TickCount - (long)lastInputInfo.dwTime);
return new TimeSpan(ticksElapsed);
}
我仍然无法仅检测到 Visual Studio 的空闲状态,例如用户可能正忙于使用另一个应用程序,但从 Visual Studio 的角度来看,用户与它交互的时间超过了配置的不活动时间。但这个解决方案目前就可以了。
为了解决这个问题,我计划跟踪 Visual Studio 何时是最顶层窗口,并在不是最顶层窗口时启动一个单独的计时器,如果达到配置的不活动时间段,将触发我想要的操作。同样,当 Visual Studio 切换到并成为最顶层窗口时,我将停止这个辅助计时器。