提问人:majixin 提问时间:9/29/2023 最后编辑:Jack J Junmajixin 更新时间:9/29/2023 访问量:43
检测 Visual Studio 扩展 (VSIX) 中特定于应用程序的用户活动
Detect application-specific user activity within Visual Studio Extension (VSIX)
问:
我想以编程方式检测用户何时从 Visual Studio 扩展 (VSIX) 中与 Visual Studio 交互。我不需要任何按键或鼠标事件信息,只需要用户在安装 VSIX 的正在运行的 Visual Studio 实例中执行某些操作的通知。我寻求一个“信号”来了解用户是否在 VS IDE 上移动鼠标,或单击任何鼠标按钮,或按下正在运行的 VS 实例将“看到”的任何键或组合键。
我为什么想知道这个? 我有一个不活动计时器在运行,如果不活动达到或超过定义的时间段,它将触发一些行为。我需要能够在用户执行任何形式的键盘/鼠标输入时重置计时器的开始时间。这不仅限于代码编辑器窗口,而且实际上是用户与正在运行的 Visual Studio 实例的任何形式的交互。因此,同样,它不是 Windows 系统范围的,但它超出了扩展 (VSIX) 本身。
目前,我正在使用系统范围的钩子,并尝试将事件消息筛选到 Visual Studio 主窗口句柄,如从 EnvDTE80 对象获取的那样。这似乎过于密集,并导致需要某种评估的过多窗口消息,并产生减慢整个环境响应能力的周期性副作用。(当系统变慢时,它不会进行垃圾回收)。
以下是我正在使用的经过净化/简化的要点:
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);
}
}
与我的实际专有代码的区别在于,如果我在钩子回调中有代码,我还尝试从中确定窗口句柄,并将其与使用引用获取的 Visual Studio 主窗口句柄进行比较,因此,从本质上讲,我只会重置我的非活动计时器,以便与安装扩展的正在运行的 Visual Studio 实例进行有效的用户交互。lParam
EnvDTE80
我想要一种更高层次的方法来实现相同的目标,理想情况下避免 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 切换到并成为最顶部的窗口时,我将停止此辅助计时器。
评论