跳到主要内容

github/tai

公开

Tai · Windows 软件与网站使用时长统计。上游 Planshit/Tai 的源码镜像,非灵栈原创;MIT 许可。来源:https://github.com/Planshit/Tai 。本次为手动同步,暂不提供安装包。

浏览文件

AppManager.cs

354 行 · 13.0 KB历史下载
1using Core.Enums;
2using Core.Librarys;
3using Core.Models.AppObserver;
4using Core.Servicers.Interfaces;
5using System;
6using System.Collections.Generic;
7using System.Diagnostics;
8using System.IO;
9using System.Linq;
10using System.Management;
11using System.Runtime.InteropServices;
12using System.Text;
13using System.Text.RegularExpressions;
14using System.Threading;
15using System.Threading.Tasks;
16
17namespace Core.Servicers.Instances
18{
19 public class AppManager : IAppManager
20 {
21 #region win32api
22 [DllImport("user32.dll", CharSet = CharSet.Auto)]
23 public static extern int GetWindowThreadProcessId(IntPtr hwnd, out int ID);
24 [DllImport("kernel32.dll", SetLastError = true)]
25 public static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, int dwProcessId);
26 [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
27 public static extern bool QueryFullProcessImageName([In] IntPtr hProcess, [In] int dwFlags, [Out] StringBuilder lpExeName, ref int lpdwSize);
28 public const int PROCESS_QUERY_INFORMATION = 0x0400;
29 public const int PROCESS_VM_READ = 0x0010;
30
31 [DllImport("psapi.dll", SetLastError = true)]
32 public static extern uint GetModuleBaseName(IntPtr hProcess, IntPtr hModule, [Out] StringBuilder lpBaseName, ref uint nSize);
33
34 [DllImport("kernel32.dll", SetLastError = true)]
35 public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
36
37 [DllImport("kernel32.dll", SetLastError = true)]
38 [return: MarshalAs(UnmanagedType.Bool)]
39 public static extern bool CloseHandle(IntPtr hObject);
40
41 [StructLayout(LayoutKind.Sequential)]
42 public struct GUITHREADINFO
43 {
44 public uint cbSize;
45 public uint flags;
46 public IntPtr hwndActive;
47 public IntPtr hwndFocus;
48 public IntPtr hwndCapture;
49 public IntPtr hwndMenuOwner;
50 public IntPtr hwndMoveSize;
51 public IntPtr hwndCaret;
52 public RECT rcCaret;
53 }
54
55 [StructLayout(LayoutKind.Sequential)]
56 public struct RECT
57 {
58 public int Left;
59 public int Top;
60 public int Right;
61 public int Bottom;
62 }
63
64 [DllImport("user32.dll")]
65 [return: MarshalAs(UnmanagedType.Bool)]
66 public static extern bool GetGUIThreadInfo(uint idThread, ref GUITHREADINFO lpgui);
67 #endregion
68
69 private Dictionary<string, AppInfo> _apps;
70 private int _outTime = 5000;
71 //private string _windowsVersionName;
72 public AppManager()
73 {
74 _apps = new Dictionary<string, AppInfo>();
75 //_windowsVersionName = SystemCommon.GetWindowsVersionName();
76 }
77
78 public AppInfo GetAppInfo(IntPtr hwnd_)
79 {
80 try
81 {
82 AppInfo app;
83 var sw = Stopwatch.StartNew();
84 GetWindowThreadProcessId(hwnd_, out int processId);
85 sw = Stopwatch.StartNew();
86 string processName = GetAppProcessName(processId);
87 string executablePath = string.Empty;
88 string description = string.Empty;
89 AppType appType = AppType.Win32;
90
91 sw = Stopwatch.StartNew();
92 if (string.IsNullOrEmpty(processName))
93 {
94 return AppInfo.Empty;
95 }
96 else if (_apps.ContainsKey(processName))
97 {
98 return _apps[processName];
99 }
100 else if (processName == "ApplicationFrameHost")
101 {
102 // uwp应用
103 // 尝试直接获取可执行文件路径,如果为空表示刚启动或者全屏状态
104 executablePath = GetUWPAPPExecutablePath(hwnd_, (uint)processId);
105 appType = AppType.UWP;
106
107 if (string.IsNullOrEmpty(executablePath))
108 {
109 // 刚启动的程序需要延迟获取
110 int duration = 0;
111 while (processName == "ApplicationFrameHost")
112 {
113 Thread.Sleep(100);
114 duration += 100;
115 if (duration >= _outTime)
116 {
117 break;
118 }
119 executablePath = GetUWPAPPExecutablePath(hwnd_, (uint)processId);
120
121 if (string.IsNullOrEmpty(executablePath))
122 {
123 // 可能是全屏状态
124 int pid = GetFullScreenUWPPID();
125 string name = GetAppProcessName(pid);
126
127 processName = string.IsNullOrEmpty(name) ? processName : name;
128 executablePath = GetAppExecutablePath(pid);
129 processId = pid;
130 }
131 else
132 {
133 processName = Path.GetFileNameWithoutExtension(executablePath);
134 }
135 }
136 }
137 else
138 {
139 processName = Path.GetFileNameWithoutExtension(executablePath);
140 }
141 }
142 //else if (_windowsVersionName.Contains("Windows 7") && processName == "dllhost")
143 //{
144 // // Windows7的COM应用
145
146 //}
147 else
148 {
149 executablePath = GetAppExecutablePath(processId);
150 }
151
152 if (!string.IsNullOrEmpty(executablePath))
153 {
154 FileVersionInfo info = FileVersionInfo.GetVersionInfo(executablePath);
155 description = info.FileDescription;
156 }
157
158 appType = IsSystemComponent(processName, hwnd_) ? AppType.SystemComponent : AppType.Win32;
159 app = new AppInfo(hwnd_, processId, processName, description, executablePath, appType);
160 if (processName != "explorer" && !_apps.ContainsKey(processName))
161 {
162 _apps.Add(processName, app);
163 }
164 return app;
165 }
166 catch (Exception ex)
167 {
168 Logger.Error(ex.ToString());
169 return AppInfo.Empty;
170 }
171 }
172
173 private string GetAppProcessName(int processId_)
174 {
175 Process process = Process.GetProcessById(processId_);
176 return process.ProcessName;
177 }
178
179 private string GetAppExecutablePath(int processId_)
180 {
181 IntPtr processHandle = IntPtr.Zero;
182 processHandle = OpenProcess(0x001F0FFF, false, processId_);
183 string executablePath = string.Empty;
184 if (processHandle != IntPtr.Zero)
185 {
186 int MaxPathLength = 4096;
187 var buffer = new StringBuilder(MaxPathLength);
188 QueryFullProcessImageName(processHandle, 0, buffer, ref MaxPathLength);
189 executablePath = buffer.ToString();
190 }
191 return executablePath;
192 }
193
194 internal struct WINDOWINFO
195 {
196 public uint ownerpid;
197 public uint childpid;
198 }
199
200 private string GetUWPAPPExecutablePath(IntPtr hWnd_, uint pID_)
201 {
202 WINDOWINFO windowinfo = new WINDOWINFO();
203 windowinfo.ownerpid = pID_;
204 windowinfo.childpid = windowinfo.ownerpid;
205
206 IntPtr pWindowinfo = Marshal.AllocHGlobal(Marshal.SizeOf(windowinfo));
207
208 Marshal.StructureToPtr(windowinfo, pWindowinfo, false);
209
210 Win32API.EnumWindowProc lpEnumFunc = new Win32API.EnumWindowProc(EnumChildWindowsCallback);
211 Win32API.EnumChildWindows(hWnd_, lpEnumFunc, pWindowinfo);
212
213 windowinfo = (WINDOWINFO)Marshal.PtrToStructure(pWindowinfo, typeof(WINDOWINFO));
214 if (windowinfo.childpid == windowinfo.ownerpid)
215 {
216 return null;
217 }
218 IntPtr proc;
219 if ((proc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, (int)windowinfo.childpid)) == IntPtr.Zero) return null;
220
221 int capacity = 2000;
222 StringBuilder sb = new StringBuilder(capacity);
223 QueryFullProcessImageName(proc, 0, sb, ref capacity);
224
225 Marshal.FreeHGlobal(pWindowinfo);
226
227 return sb.ToString(0, capacity);
228 }
229 private bool EnumChildWindowsCallback(IntPtr hWnd_, IntPtr lParam_)
230 {
231 WINDOWINFO info = (WINDOWINFO)Marshal.PtrToStructure(lParam_, typeof(WINDOWINFO));
232
233 int pID;
234 GetWindowThreadProcessId(hWnd_, out pID);
235
236 if (pID != info.ownerpid) info.childpid = (uint)pID;
237
238 Marshal.StructureToPtr(info, lParam_, true);
239
240 return true;
241 }
242
243 private IntPtr getThreadWindowHandle(uint dwThreadId_)
244 {
245 IntPtr hWnd;
246
247 // Get Window Handle and title from Thread
248 var guiThreadInfo = new GUITHREADINFO();
249 guiThreadInfo.cbSize = (uint)Marshal.SizeOf(guiThreadInfo);
250
251 GetGUIThreadInfo(dwThreadId_, ref guiThreadInfo);
252
253 hWnd = guiThreadInfo.hwndFocus;
254 //some times while changing the focus between different windows, it returns Zero so we would return the Active window in that case
255 if (hWnd == IntPtr.Zero)
256 {
257 hWnd = guiThreadInfo.hwndActive;
258 }
259 return hWnd;
260 }
261
262 #region 获取全屏UWP应用PID
263 /// <summary>
264 /// 获取全屏UWP应用PID
265 /// </summary>
266 /// <returns></returns>
267 private int GetFullScreenUWPPID()
268 {
269 var current = getThreadWindowHandle(0);
270 int processId = 0;
271 GetWindowThreadProcessId(current, out processId);
272 return processId;
273 }
274 #endregion
275
276 #region 判断应用是否是系统组件
277 private bool IsSystemComponent(string processName_, IntPtr windowHandle_)
278 {
279 // 系统组件类名
280 string[] sysClassNameArr = {
281 "Progman",
282 "WorkerW",
283 "Shell_TrayWnd",
284 "XamlExplorerHostIslandWindow",
285 "TopLevelWindowForOverflowXamlIsland",
286 "Shell_InputSwitchTopLevelWindow",
287 "LockScreenControllerProxyWindow",
288 // win7
289 "DV2ControlHost",
290 "Button"
291 };
292 // 系统进程名称
293 string[] sysProcessArr = {
294 "ShellExperienceHost",
295 "StartMenuExperienceHost",
296 "SearchHost",
297 "LockApp"
298 };
299
300 // 在windows7下,dllhost进程可能是Windows照片查看器,classname为:Photo_Lightweight_Viewer
301 // 需要单独为windows7写一个判定器,然后加一个转换器,dllhost进程->windows照片查看器
302 // explorer,class:CabinetWClass在win7下可能是文件夹、控制面板、我的电脑属性
303 bool isSys;
304 if (processName_ == "explorer")
305 {
306 // 先通过类名判定
307 string className = Win32API.GetWindowClassName(windowHandle_);
308 Logger.Info($"IsSystemComponent,process:{processName_},className:{className}");
309 if (!string.IsNullOrEmpty(className))
310 {
311 isSys = sysClassNameArr.Contains(className);
312 }
313 else
314 {
315 int titleLength = Win32API.GetWindowTextLength(windowHandle_);
316 isSys = titleLength <= 0;
317 }
318 }
319 else
320 {
321 // 通过进程名称判定
322 isSys = sysProcessArr.Contains(processName_);
323 }
324 return isSys;
325 }
326 #endregion
327
328 #region 获取COM应用
329 private AppInfo GetCOMAPP(int processId_)
330 {
331 try
332 {
333 string wmiQuery = string.Format("select ProcessId,CommandLine from Win32_Process where ProcessId='{0}'", processId_);
334 ManagementObjectSearcher searcher = new ManagementObjectSearcher(wmiQuery);
335 var enu = searcher.Get().GetEnumerator();
336 if (enu.MoveNext())
337 {
338 var commandLine = enu.Current["CommandLine"];
339 string appId = Regex.Match(commandLine.ToString(), @"\{(.*?)\}").Value;
340 if (!string.IsNullOrEmpty(appId))
341 {
342
343 }
344 }
345 }
346 catch (Exception ex)
347 {
348 Logger.Error(ex.ToString());
349 }
350 return null;
351 }
352 #endregion
353 }
354}