소스 검색

:zap: 增加多文件备份, 尝试修复UI假死状态, 修复高分屏缩放导致的鼠标追随bug

BookerLiu 1 년 전
부모
커밋
a61c69aa0b
14개의 변경된 파일504개의 추가작업 그리고 85개의 파일을 삭제
  1. 2 1
      App.config
  2. 28 0
      App.xaml.cs
  3. 5 1
      Constant/Constants.cs
  4. 1 1
      GeekDesk.csproj
  5. 12 12
      MainWindow.xaml.cs
  6. 1 1
      MyThread/RelativePathThread.cs
  7. 2 2
      Properties/AssemblyInfo.cs
  8. 92 0
      Task/BakTask.cs
  9. 0 31
      Task/ShowSecondTask.cs
  10. 3 3
      Update.json
  11. 87 33
      Util/CommonCode.cs
  12. 17 0
      Util/ProcessUtil.cs
  13. 89 0
      Util/ShowWindowFollowMouse.cs
  14. 165 0
      Util/WindowUtil.cs

+ 2 - 1
App.config

@@ -61,7 +61,7 @@
 		</assemblyBinding>
 	</runtime>
 	<appSettings>
-		<add key="Version" value="2.5.14" />
+		<add key="Version" value="2.5.15" />
 		<add key="GitHubUrl" value="https://github.com/BookerLiu/GeekDesk" />
 		<add key="GiteeUrl" value="https://gitee.com/BookerLiu/GeekDesk/tree/master" />
 		<add key="GitHubUpdateUrl" value="https://raw.githubusercontent.com/BookerLiu/GeekDesk/master/Update.json" />
@@ -70,6 +70,7 @@
 		<add key="ClientSettingsProvider.ServiceUri" value="" />
 		<add key="CustomIconTeachUrl" value="https://mp.weixin.qq.com/s/LxoHAekho9HBVl4FRw_Law" />
 		<add key="ShowPublicWeChat" value="Y" />
+		<add key="BakDays" value="7" />
 	</appSettings>
 	<system.web>
 		<membership defaultProvider="ClientAuthenticationMembershipProvider">

+ 28 - 0
App.xaml.cs

@@ -1,8 +1,14 @@
 using GeekDesk.Constant;
+using GeekDesk.MyThread;
 using GeekDesk.Util;
+using GeekDesk.ViewModel;
+using Microsoft.Win32;
 using System;
+using System.Diagnostics;
 using System.Windows;
 using System.Windows.Input;
+using System.Windows.Interop;
+using System.Windows.Media;
 using System.Windows.Threading;
 
 namespace GeekDesk
@@ -20,10 +26,13 @@ namespace GeekDesk
             this.Startup += new StartupEventHandler(App_Startup);
             Application.Current.DispatcherUnhandledException += Current_DispatcherUnhandledException;
             AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
+            SystemEvents.PowerModeChanged += OnPowerModeChanged;
         }
 
+
         private void App_Startup(object sender, StartupEventArgs e)
         {
+            //RenderOptions.ProcessRenderMode = System.Windows.Interop.RenderMode.SoftwareOnly; //禁用硬件加速
             mutex = new System.Threading.Mutex(true, Constants.MY_NAME, out bool ret);
             if (!ret)
             {
@@ -40,6 +49,25 @@ namespace GeekDesk
             }
         }
 
+
+    //电源监听
+    private void OnPowerModeChanged(object sender, PowerModeChangedEventArgs e)
+        {
+            switch (e.Mode)
+            {
+                case PowerModes.Resume:
+                    // 系统从休眠状态唤醒
+                    LogUtil.WriteLog("System resumed from sleep.");
+                    ProcessUtil.ReStartApp();
+                    break;
+                case PowerModes.Suspend:
+                    // 系统进入休眠状态
+                    LogUtil.WriteLog("System is going to sleep.");
+                    break;
+            }
+        }
+        
+
         void Current_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
         {
             e.Handled = true;//使用这一行代码告诉运行时,该异常被处理了,不再作为UnhandledException抛出了。

+ 5 - 1
Constant/Constants.cs

@@ -22,7 +22,11 @@ namespace GeekDesk.Constant
         /// <summary>
         /// 备份文件路径
         /// </summary>
-        public static string DATA_FILE_BAK_PATH = APP_DIR + "bak\\Data.bak";  //app备份数据文件路径
+        public static string DATA_FILE_BAK_DIR_PATH = APP_DIR + "bak";  //app备份数据文件路径
+        public static string DATA_FILE_TEMP_DIR_PATH = APP_DIR + "temp";  //app临时缓存文件路径
+
+        //public static string DATA_FILE_BAK_PATH = DATA_FILE_BAK_DIR_PATH + "\\Data.bak";  //app备份数据文件路径
+
 
         public static string PW_FILE_BAK_PATH = APP_DIR + "bak\\pw.txt";  //密码文件路径
 

+ 1 - 1
GeekDesk.csproj

@@ -290,7 +290,7 @@
       <DependentUpon>SecondsWindow.xaml</DependentUpon>
     </Compile>
     <Compile Include="Plugins\ShowSeconds\ViewModel\SecondsDataContext.cs" />
-    <Compile Include="Task\ShowSecondTask.cs" />
+    <Compile Include="Task\BakTask.cs" />
     <Compile Include="Task\ToDoTask.cs" />
     <Compile Include="MyThread\MouseHookThread.cs" />
     <Compile Include="MyThread\DispatcherBuild.cs" />

+ 12 - 12
MainWindow.xaml.cs

@@ -73,8 +73,11 @@ namespace GeekDesk
                 MarginHide.StartHide();
             }
 
+
+
         }
 
+
         private void Window_SourceInitialized(object sender, EventArgs e)
         {
             try
@@ -85,6 +88,8 @@ namespace GeekDesk
             catch (Exception) { }
         }
 
+       
+
 
 
         /// <summary>
@@ -379,6 +384,9 @@ namespace GeekDesk
                 EveryThingUtil.EnableEveryThing();
             }
 
+            //启动文件备份任务
+            BakTask.Start();
+
             Keyboard.Focus(SearchBox);
 
             MessageUtil.ChangeWindowMessageFilter(MessageUtil.WM_COPYDATA, 1);
@@ -388,6 +396,7 @@ namespace GeekDesk
             {
                 Guide();
             }
+           
         }
 
 
@@ -596,6 +605,7 @@ namespace GeekDesk
             if (appData.AppConfig.FollowMouse)
             {
                 ShowWindowFollowMouse.Show(mainWindow, MousePosition.CENTER, 0, 0);
+                //ShowWindowFollowMouse.FollowMouse(mainWindow);
             }
 
 
@@ -853,19 +863,9 @@ namespace GeekDesk
         /// </summary>
         /// <param name="sender"></param>
         /// <param name="e"></param>
-        private void ReStartApp(object sender, RoutedEventArgs e)
+        public void ReStartApp(object sender, RoutedEventArgs e)
         {
-            if (appData.AppConfig.MouseMiddleShow || appData.AppConfig.SecondsWindow == true)
-            {
-                MouseHookThread.Dispose();
-            }
-
-            Process p = new Process();
-            p.StartInfo.FileName = Constants.APP_DIR + "GeekDesk.exe";
-            p.StartInfo.WorkingDirectory = Constants.APP_DIR;
-            p.Start();
-
-            Application.Current.Shutdown();
+            ProcessUtil.ReStartApp();
         }
 
         /// <summary>

+ 1 - 1
MyThread/RelativePathThread.cs

@@ -36,7 +36,7 @@ namespace GeekDesk.MyThread
                         }
                     }
                     CommonCode.SaveAppData(MainWindow.appData, Constants.DATA_FILE_PATH);
-                    CommonCode.SaveAppData(MainWindow.appData, Constants.DATA_FILE_BAK_PATH);
+                    //CommonCode.SaveAppData(MainWindow.appData, Constants.DATA_FILE_BAK_PATH);
                 }
                 catch (Exception ex)
                 {

+ 2 - 2
Properties/AssemblyInfo.cs

@@ -49,5 +49,5 @@ using System.Windows;
 //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
 //通过使用 "*",如下所示:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("2.5.1.3")]
-[assembly: AssemblyFileVersion("2.5.1.3")]
+[assembly: AssemblyVersion("2.5.1.5")]
+[assembly: AssemblyFileVersion("2.5.1.5")]

+ 92 - 0
Task/BakTask.cs

@@ -0,0 +1,92 @@
+using GeekDesk.Constant;
+using GeekDesk.ViewModel;
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace GeekDesk.Task
+{
+    internal class BakTask
+    {
+
+        public static void Start()
+        {
+            System.Timers.Timer timer = new System.Timers.Timer
+            {
+                Enabled = true,
+                Interval = 60 * 1000 * 60, //60秒 * 60分钟
+                //Interval = 6000,
+            };
+            timer.Start();
+            timer.Elapsed += Timer_Elapsed;
+        }
+
+        private static void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
+        {
+            DirectoryInfo dirInfo = new DirectoryInfo(Constants.DATA_FILE_BAK_DIR_PATH);
+
+            string todayBakName = DateTime.Now.ToString("yyyy-MM-dd") + ".bak";
+
+            string bakDaysStr = ConfigurationManager.AppSettings["BakDays"];
+            int bakDays = 7;
+            if (bakDaysStr != null && !"".Equals(bakDaysStr.Trim()))
+            {
+                bakDays = Int32.Parse(bakDaysStr.Trim());
+            }
+
+            string bakFilePath = Constants.DATA_FILE_BAK_DIR_PATH + "\\" + todayBakName;
+            if (dirInfo.Exists)
+            {
+                // 获取文件信息并按创建时间倒序排序
+                FileInfo[] files = dirInfo.GetFiles();
+                if (files.Length > 0)
+                {
+                    FileInfo[] sortedFiles = files.OrderByDescending(file => file.CreationTime).ToArray();
+                    if (!sortedFiles[0].Name.Equals(todayBakName))
+                    {
+                        //今天未创建备份  开始创建今天的备份
+                        createBakFile(bakFilePath);
+                    }
+
+                    //判断文件是否超过了7个  超过7个就删除
+                    if (sortedFiles.Length > bakDays)
+                    {
+                        for (int i = bakDays; i < sortedFiles.Length; i++)
+                        {
+                            sortedFiles[i].Delete();
+                        }
+                    }
+
+                } else
+                {
+                    //没有文件 直接创建今天的备份
+                    createBakFile(bakFilePath);
+                }
+                
+            }
+            else
+            {
+                dirInfo.Create();
+            }
+        }
+
+        //创建备份文件
+        private static void createBakFile(string filePath)
+        {
+            using (FileStream fs = new FileStream(filePath, FileMode.Create))
+            {
+                BinaryFormatter bf = new BinaryFormatter();
+                bf.Serialize(fs, MainWindow.appData);
+            }
+        }
+
+
+    }
+}

+ 0 - 31
Task/ShowSecondTask.cs

@@ -1,31 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace GeekDesk.Task
-{
-    internal class ShowSecondTask
-    {
-
-        public static void SHowSecond()
-        {
-            System.Timers.Timer timer = new System.Timers.Timer
-            {
-                Enabled = true,
-                Interval = 5000
-            };
-            timer.Start();
-            timer.Elapsed += Timer_Elapsed;
-        }
-
-        private static void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
-        {
-            Process[] pcArr = Process.GetProcessesByName("ShellExperienceHost.exe");
-            Thread.Sleep(1000);
-        }
-    }
-}

+ 3 - 3
Update.json

@@ -1,10 +1,10 @@
 {
 	"title": "GeekDesk版本更新",
-	"subTitle": "V2.5.14",
+	"subTitle": "V2.5.15",
 	"msgTitle": "本次更新内容如下",
-	"msg": "['好久不见, 别来无恙, 辞职回老家了, 我是个多愁善感的人, 心里有些仿徨和迷茫, 祝所有人都前程似锦, 世界和平.', 'GeekDesk准备冲击一下Gitee GVP, 希望大家能给我点一下码云(Gitee)和GitHub的star❤❤❤', '之后我会抽时间编写一下开发者文档, 方便大家更清楚的了解项目结构, 从而有更多的人参与进来开发(一直没有编写是因为太懒了), 不多说了, 看下这次更新内容吧', '集成Everything搜索,设置-->其它-->勾选Everything插件开启', '增加了关联文件夹功能, 右键点击左侧栏-->新建关联菜单', '增加强制置顶开关,设置-->显示设置-->勾选/取消 置于顶层', '右侧栏图标列表增加了自适应列宽, 不会出现图标显示一半的情况了', '简单添加了新手引导提示', '加密菜单bug修复 By @1062406901', '多显示器拾色器bug修复 By @1062406901', '拖动图标到菜单的异常修复 By @Hsxxxxxx', '优化部分UI', '其它bug修复及功能优化','关注微信公众号\\'抓几个娃\\'可以第一时间收到更新通知(公众号也是佛系维护, 希望能关注的人多一点吧, 让作者这个穷B挣口饭吃)']",
+	"msg": "['鸽了挺久, 我终于又来更新了, 废话不多说, 看下本次更新内容', '增加多文件备份, 数据文件损坏会自动寻找最近的一次备份数据, 不用担心数据文件损坏了, 备份数据默认为最近7天, 可以通过打开根目录下GeekDesk.exe.config, 找到BakDays修改', '修复高分屏缩放导致的鼠标追随bug', 'UI假死的情况, 由于并不是每个用户都出现这个bug, 这次做了尝试性修复, 各位可以看下是否还会出现切换菜单出现不会出现图标的情况']",
 	"githubUrl": "https://github.com/BookerLiu/GeekDesk/releases",
 	"giteeUrl": "https://gitee.com/BookerLiu/GeekDesk/releases",
 	"statisticUrl": "http://43.138.23.39:8989/bookerService/geekDeskController/userCountStatistic",
-	"version": "2.5.14"
+	"version": "2.5.15"
 }

+ 87 - 33
Util/CommonCode.cs

@@ -8,6 +8,7 @@ using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.IO;
+using System.Linq;
 using System.Runtime.InteropServices;
 using System.Runtime.Serialization.Formatters.Binary;
 using System.Windows;
@@ -34,6 +35,7 @@ namespace GeekDesk.Util
                 using (FileStream fs = File.Create(Constants.DATA_FILE_PATH)) { }
                 appData = new AppData();
                 SaveAppData(appData, Constants.DATA_FILE_PATH);
+                return appData;
             }
             else
             {
@@ -49,51 +51,103 @@ namespace GeekDesk.Util
                         {
                             SavePassword(appData.AppConfig.MenuPassword);
                         }
+                        return appData;
                     }
                 }
                 catch
                 {
-                    if (File.Exists(Constants.DATA_FILE_BAK_PATH))
+                    DirectoryInfo dirInfo = new DirectoryInfo(Constants.DATA_FILE_BAK_DIR_PATH);
+                    FileInfo[] files = dirInfo.GetFiles();
+                    if (files.Length > 0)
                     {
-                        try
+                        FileInfo[] sortedFiles = files.OrderByDescending(file => file.CreationTime).ToArray();
+                        //循环获取可用备份文件
+                        string bakFilePath = "";
+                        foreach (FileInfo bakFile in sortedFiles)
                         {
-                            using (FileStream fs = new FileStream(Constants.DATA_FILE_BAK_PATH, FileMode.Open))
+                            if (!Directory.Exists(Constants.DATA_FILE_TEMP_DIR_PATH)) { Directory.CreateDirectory(Constants.DATA_FILE_TEMP_DIR_PATH); }
+                            bakFilePath = Constants.DATA_FILE_TEMP_DIR_PATH + "\\" + bakFile.Name;
+                            try
                             {
-                                BinaryFormatter bf = new BinaryFormatter();
-                                appData = bf.Deserialize(fs) as AppData;
+                                File.Copy(bakFile.FullName, bakFilePath, true);
+                                using (FileStream fs = new FileStream(bakFilePath, FileMode.Open))
+                                {
+                                    BinaryFormatter bf = new BinaryFormatter();
+                                    appData = bf.Deserialize(fs) as AppData;
+                                }
+                                DialogMsg msg = new DialogMsg();
+                                msg.msg = "不幸的是, GeekDesk当前的数据文件已经损坏, " +
+                                    "现在已经启用系统自动备份的数据\n\n" +
+                                    "如果你有较新的备份, " +
+                                    "请退出GeekDesk, " +
+                                    "将备份文件重命名为:Data, " +
+                                    "然后将Data覆盖到GeekDesk的根目录即可\n\n" +
+                                    "启用的备份文件为: \n" + bakFilePath +
+                                    "\n\n如果当前数据就是你想要的数据, 那么请不用管它";
+                                GlobalMsgNotification gm = new GlobalMsgNotification(msg);
+                                HandyControl.Controls.Notification ntf = HandyControl.Controls.Notification.Show(gm, ShowAnimation.Fade, true);
+                                gm.ntf = ntf;
+                                File.Delete(bakFilePath);
+                                SaveAppData(appData, Constants.DATA_FILE_PATH);
+                                return appData;
+                            }
+                            catch { 
+                                if (File.Exists(bakFilePath))
+                                {
+                                    File.Delete(bakFilePath);
+                                }
                             }
-
-                            DialogMsg msg = new DialogMsg();
-                            msg.msg = "不幸的是, GeekDesk当前的数据文件已经损坏, " +
-                                "现在已经启用系统自动备份的数据\n\n" +
-                                "如果你有较新的备份, " +
-                                "请退出GeekDesk, " +
-                                "将备份文件重命名为:Data, " +
-                                "然后将Data覆盖到GeekDesk的根目录即可\n\n" +
-                                "系统上次备份时间: \n" + appData.AppConfig.SysBakTime +
-                                "\n\n如果当前数据就是你想要的数据, 那么请不用管它";
-                            GlobalMsgNotification gm = new GlobalMsgNotification(msg);
-                            HandyControl.Controls.Notification ntf = HandyControl.Controls.Notification.Show(gm, ShowAnimation.Fade, true);
-                            gm.ntf = ntf;
-                        }
-                        catch
-                        {
-                            MessageBox.Show("不幸的是, GeekDesk当前的数据文件已经损坏\n如果你有备份, 请将备份文件重命名为:Data 然后将Data覆盖到GeekDesk的根目录即可!");
-                            Application.Current.Shutdown();
-                            return null;
                         }
-
-                    }
-                    else
+                        MessageBox.Show("不幸的是, GeekDesk当前的数据文件已经损坏\n如果你有备份, 请将备份文件重命名为:Data 然后将Data覆盖到GeekDesk的根目录即可!");
+                        Application.Current.Shutdown();
+                        return new AppData();
+                    } else
                     {
                         MessageBox.Show("不幸的是, GeekDesk当前的数据文件已经损坏\n如果你有备份, 请将备份文件重命名为:Data 然后将Data覆盖到GeekDesk的根目录即可!");
                         Application.Current.Shutdown();
-                        return null;
+                        return new AppData();
                     }
 
+                    //    if (File.Exists(Constants.DATA_FILE_BAK_PATH))
+                    //{
+                    //    try
+                    //    {
+                    //        using (FileStream fs = new FileStream(Constants.DATA_FILE_BAK_PATH, FileMode.Open))
+                    //        {
+                    //            BinaryFormatter bf = new BinaryFormatter();
+                    //            appData = bf.Deserialize(fs) as AppData;
+                    //        }
+
+                    //        DialogMsg msg = new DialogMsg();
+                    //        msg.msg = "不幸的是, GeekDesk当前的数据文件已经损坏, " +
+                    //            "现在已经启用系统自动备份的数据\n\n" +
+                    //            "如果你有较新的备份, " +
+                    //            "请退出GeekDesk, " +
+                    //            "将备份文件重命名为:Data, " +
+                    //            "然后将Data覆盖到GeekDesk的根目录即可\n\n" +
+                    //            "系统上次备份时间: \n" + appData.AppConfig.SysBakTime +
+                    //            "\n\n如果当前数据就是你想要的数据, 那么请不用管它";
+                    //        GlobalMsgNotification gm = new GlobalMsgNotification(msg);
+                    //        HandyControl.Controls.Notification ntf = HandyControl.Controls.Notification.Show(gm, ShowAnimation.Fade, true);
+                    //        gm.ntf = ntf;
+                    //    }
+                    //    catch
+                    //    {
+                    //        MessageBox.Show("不幸的是, GeekDesk当前的数据文件已经损坏\n如果你有备份, 请将备份文件重命名为:Data 然后将Data覆盖到GeekDesk的根目录即可!");
+                    //        Application.Current.Shutdown();
+                    //        return null;
+                    //    }
+
+                    //}
+                    //else
+                    //{
+                    //    MessageBox.Show("不幸的是, GeekDesk当前的数据文件已经损坏\n如果你有备份, 请将备份文件重命名为:Data 然后将Data覆盖到GeekDesk的根目录即可!");
+                    //    Application.Current.Shutdown();
+                    //    return null;
+                    //}
+
                 }
             }
-            return appData;
         }
 
         private readonly static object _MyLock = new object();
@@ -105,10 +159,10 @@ namespace GeekDesk.Util
         {
             lock (_MyLock)
             {
-                if (filePath.Equals(Constants.DATA_FILE_BAK_PATH))
-                {
-                    appData.AppConfig.SysBakTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
-                }
+                //if (filePath.Equals(Constants.DATA_FILE_BAK_PATH))
+                //{
+                //    appData.AppConfig.SysBakTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+                //}
                 if (!Directory.Exists(filePath.Substring(0, filePath.LastIndexOf("\\"))))
                 {
                     Directory.CreateDirectory(filePath.Substring(0, filePath.LastIndexOf("\\")));

+ 17 - 0
Util/ProcessUtil.cs

@@ -1,4 +1,5 @@
 using GeekDesk.Constant;
+using GeekDesk.MyThread;
 using GeekDesk.ViewModel;
 using HandyControl.Controls;
 using System;
@@ -9,6 +10,7 @@ using System.Linq;
 using System.Runtime.InteropServices;
 using System.Text;
 using System.Threading.Tasks;
+using System.Windows;
 
 namespace GeekDesk.Util
 {
@@ -241,6 +243,21 @@ namespace GeekDesk.Util
         }
 
 
+        public static void ReStartApp()
+        {
+            if (MainWindow.appData.AppConfig.MouseMiddleShow || MainWindow.appData.AppConfig.SecondsWindow == true)
+            {
+                MouseHookThread.Dispose();
+            }
+
+            Process p = new Process();
+            p.StartInfo.FileName = Constants.APP_DIR + "GeekDesk.exe";
+            p.StartInfo.WorkingDirectory = Constants.APP_DIR;
+            p.Start();
+
+            Application.Current.Shutdown();
+        }
+
         [Flags]
         private enum ProcessAccessFlags : uint
         {

+ 89 - 0
Util/ShowWindowFollowMouse.cs

@@ -1,6 +1,9 @@
 using GeekDesk.Constant;
 using System;
+using System.Drawing;
+using System.Runtime.InteropServices;
 using System.Windows;
+using System.Windows.Forms;
 
 namespace GeekDesk.Util
 {
@@ -18,6 +21,86 @@ namespace GeekDesk.Util
             RIGHT_CENTER = 7
         }
 
+        public static void FollowMouse(Window window)
+        {
+            // Get the mouse position
+            var mousePosition = System.Windows.Forms.Control.MousePosition;
+
+            // Get the window size
+            var windowWidth = window.Width;
+            var windowHeight = window.Height;
+
+            Console.WriteLine("windowWidth +  windowHeight:" + windowWidth + "+" +  windowHeight);
+
+            // Get the screen where the mouse is located
+            var screen = System.Windows.Forms.Screen.FromPoint(new System.Drawing.Point(mousePosition.X, mousePosition.Y));
+            var workingArea = screen.WorkingArea;
+
+            // Get the DPI scaling factor for the screen
+            //float dpiX, dpiY;
+            //using (var graphics = System.Drawing.Graphics.FromHwnd(IntPtr.Zero))
+            //{
+            //    dpiX = graphics.DpiX / 96f; // 96 is the default DPI
+            //    dpiY = graphics.DpiY / 96f; // 96 is the default DPI
+            //}
+
+            float dpiX = GetDpi( true);
+            float dpiY = GetDpi(false);
+
+            // Convert mouse position to logical pixels based on DPI
+            double mouseX = mousePosition.X / dpiX;
+            double mouseY = mousePosition.Y / dpiY;
+
+            // Calculate target position to center the window on the mouse
+            double targetLeft = mouseX - windowWidth / 2;
+            double targetTop = mouseY - windowHeight / 2;
+
+            // Ensure the window does not exceed the screen boundaries
+            if (targetLeft < workingArea.Left / dpiX)
+                targetLeft = workingArea.Left / dpiX;
+            if (targetLeft + windowWidth / dpiX > workingArea.Right / dpiX)
+                targetLeft = workingArea.Right / dpiX - windowWidth / dpiX;
+
+            if (targetTop < workingArea.Top / dpiY)
+                targetTop = workingArea.Top / dpiY;
+            if (targetTop + windowHeight / dpiY > workingArea.Bottom / dpiY)
+                targetTop = workingArea.Bottom / dpiY - windowHeight / dpiY;
+
+            // Update window position
+            window.Left = targetLeft * dpiX;
+            window.Top = targetTop * dpiY;
+        }
+
+        private static float GetDpi(bool isX)
+        {
+            IntPtr hdc = WindowUtil.GetDC(IntPtr.Zero);
+            int dpi = isX ? WindowUtil.GetDeviceCaps(hdc, LOGPIXELSX) : WindowUtil.GetDeviceCaps(hdc, LOGPIXELSY);
+            WindowUtil.ReleaseDC(IntPtr.Zero, hdc);
+            return dpi / 96f;
+        }
+
+        private static IntPtr GetScreenHandleFromMouse()
+        {
+            // Get the mouse position
+            var mousePosition = System.Windows.Forms.Control.MousePosition;
+
+            // Convert mouse position to a POINT structure
+            System.Drawing.Point point = new System.Drawing.Point(mousePosition.X, mousePosition.Y);
+
+            // Get the window handle from the point
+            IntPtr windowHandle = WindowUtil.WindowFromPoint(point);
+
+            return windowHandle;
+        }
+
+        // Constants for DPI
+        private const int HORZRES = 8;
+        private const int VERTRES = 10;
+        private const int LOGPIXELSX = 88;
+        private const int LOGPIXELSY = 90;
+
+       
+
         /// <summary>
         /// 随鼠标位置显示面板 
         /// </summary>
@@ -25,6 +108,12 @@ namespace GeekDesk.Util
         {
             //获取鼠标位置
             System.Windows.Point p = MouseUtil.GetMousePosition();
+
+            float dpiX = GetDpi(true);
+            float dpiY = GetDpi(false);
+            p.X = p.X / dpiX;
+            p.Y = p.Y / dpiY;
+
             double left = SystemParameters.VirtualScreenLeft;
             double top = SystemParameters.VirtualScreenTop;
             double width = SystemParameters.VirtualScreenWidth;

+ 165 - 0
Util/WindowUtil.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
+using System.Drawing;
 using System.Linq;
 using System.Runtime.InteropServices;
 using System.Text;
@@ -9,6 +10,7 @@ using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Forms;
 using System.Windows.Interop;
+using static GeekDesk.Util.FileIcon;
 
 namespace GeekDesk.Util
 {
@@ -42,6 +44,9 @@ namespace GeekDesk.Util
             SWP_NOSENDCHANGING = 0x0400
         }
 
+        [DllImport("user32.dll")]
+        public static extern IntPtr WindowFromPoint(System.Drawing.Point p);
+
         //取得前台窗口句柄函数 
         [DllImport("user32.dll")]
         public static extern IntPtr GetForegroundWindow();
@@ -71,6 +76,60 @@ namespace GeekDesk.Util
         public static extern int GetWindowLong(IntPtr hWnd, int nIndex);
         [DllImport("user32.dll")]
         public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
+        [DllImport("User32.dll", CharSet = CharSet.Auto)]
+        public static extern int GetWindowThreadProcessId(IntPtr hwnd, out int ID);   //获取线程ID
+        /// <summary>
+        /// 枚举窗口时的委托参数
+        /// </summary>
+        /// <param name="hWnd"></param>
+        /// <param name="lParam"></param>
+        /// <returns></returns>
+        public delegate bool WndEnumProc(IntPtr hWnd, int lParam);
+        /// <summary>
+        /// 枚举所有窗口
+        /// </summary>
+        /// <param name="lpEnumFunc"></param>
+        /// <param name="lParam"></param>
+        /// <returns></returns>
+        [DllImport("user32.dll")]
+        public static extern bool EnumWindows(WndEnumProc lpEnumFunc, int lParam);
+
+        /// <summary>
+        /// 获取窗口的父窗口句柄
+        /// </summary>
+        /// <param name="hWnd"></param>
+        /// <returns></returns>
+        [DllImport("user32.dll")]
+        public static extern IntPtr GetParent(IntPtr hWnd);
+
+        [DllImport("user32.dll")]
+        public static extern bool IsWindowVisible(IntPtr hWnd);
+        [DllImport("user32.dll")]
+        public static extern int GetClassName(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
+
+        [DllImport("user32.dll")]
+        public static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab);
+
+        [DllImport("user32.dll")]
+        public static extern bool GetWindowRect(IntPtr hWnd, ref LPRECT rect);
+
+        // Import GetDC and ReleaseDC functions from user32.dll
+        [DllImport("user32.dll")]
+        public static extern IntPtr GetDC(IntPtr hWnd);
+
+        [DllImport("gdi32.dll")]
+        public static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
+
+
+        [StructLayout(LayoutKind.Sequential)]
+        public readonly struct LPRECT
+        {
+            public readonly int Left;
+            public readonly int Top;
+            public readonly int Right;
+            public readonly int Bottom;
+        }
+
 
 
         private const int GWL_STYLE = -16;
@@ -82,6 +141,112 @@ namespace GeekDesk.Util
             SetWindowLong(hwnd, GWL_STYLE, (int)(value & ~WS_MAXIMIZEBOX));
         }
 
+        /// <summary>
+        /// 获取 Win32 窗口的一些基本信息。
+        /// </summary>
+        public struct WindowInfo
+        {
+            public WindowInfo(IntPtr hWnd, string className, string title, bool isVisible, Rectangle bounds) : this()
+            {
+                Hwnd = hWnd;
+                ClassName = className;
+                Title = title;
+                IsVisible = isVisible;
+                Bounds = bounds;
+            }
+
+            /// <summary>
+            /// 获取窗口句柄。
+            /// </summary>
+            public IntPtr Hwnd { get; }
+
+            /// <summary>
+            /// 获取窗口类名。
+            /// </summary>
+            public string ClassName { get; }
+
+            /// <summary>
+            /// 获取窗口标题。
+            /// </summary>
+            public string Title { get; }
+
+            /// <summary>
+            /// 获取当前窗口是否可见。
+            /// </summary>
+            public bool IsVisible { get; }
+
+            /// <summary>
+            /// 获取窗口当前的位置和尺寸。
+            /// </summary>
+            public Rectangle Bounds { get; }
+
+            /// <summary>
+            /// 获取窗口当前是否是最小化的。
+            /// </summary>
+            public bool IsMinimized => Bounds.Left == -32000 && Bounds.Top == -32000;
+        }
+
+
+        /// <summary>
+        /// 遍历窗体处理的函数
+        /// </summary>
+        /// <param name="hWnd"></param>
+        /// <param name="lparam"></param>
+        /// <returns></returns>
+        private static bool OnWindowEnum(IntPtr hWnd, int lparam)
+        {
+
+            // 仅查找顶层窗口。
+            //if (GetParent(hWnd) == IntPtr.Zero)
+            //{
+                // 获取窗口类名。
+                var lpString = new StringBuilder(512);
+                GetClassName(hWnd, lpString, lpString.Capacity);
+                var className = lpString.ToString();
+
+                // 获取窗口标题。
+                var lptrString = new StringBuilder(512);
+                GetWindowText(hWnd, lptrString, lptrString.Capacity);
+                var title = lptrString.ToString().Trim();
+
+                // 获取窗口可见性。
+                var isVisible = IsWindowVisible(hWnd);
+
+                // 获取窗口位置和尺寸。
+                LPRECT rect = default;
+                GetWindowRect(hWnd, ref rect);
+                var bounds = new Rectangle(rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top);
+
+                // 添加到已找到的窗口列表。
+                windowList.Add(new WindowInfo(hWnd, className, title, isVisible, bounds));
+            //}
+
+            return true;
+        }
+        /// <summary>
+        /// 默认的查找窗口的过滤条件。可见 + 非最小化 + 包含窗口标题。
+        /// </summary>
+        private static readonly Predicate<WindowInfo> DefaultPredicate = x => x.IsVisible && !x.IsMinimized && x.Title.Length > 0;
+        /// <summary>
+        /// 窗体列表
+        /// </summary>
+        private static List<WindowInfo> windowList;
+
+
+        /// <summary>
+        /// 查找当前用户空间下所有符合条件的(顶层)窗口。如果不指定条件,将仅查找可见且有标题栏的窗口。
+        /// </summary>
+        /// <param name="match">过滤窗口的条件。如果设置为 null,将仅查找可见和标题栏不为空的窗口。</param>
+        /// <returns>找到的所有窗口信息</returns>
+        public static IReadOnlyList<WindowInfo> FindAllWindows(Predicate<WindowInfo> match = null)
+        {
+            windowList = new List<WindowInfo>();
+            //遍历窗口并查找窗口相关WindowInfo信息
+            EnumWindows(OnWindowEnum, 0);
+            return windowList;
+        }
+
+
 
         public static void SetOwner(Window window, Window parentWindow)
         {