diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6059533e2..56b7b9de0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: key: Conan-${{ hashFiles('src/**/conanfile.txt') }}-${{ matrix.platform }} - name: Build - run: python publish.py ${{ matrix.platform }} + run: python publish.py ${{ matrix.platform }} unpackaged certs\Magpie.pfx "${{ secrets.MAGPIE_PFX_PASSWORD }}" - name: Save hash id: hash diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 98f5ef198..d03bb7c70 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,7 +45,7 @@ jobs: uses: actions/cache@v4 with: path: ~/.conan2/p - key: ${{ runner.os }}-conan-${{ hashFiles('src/**/conanfile.txt') }} + key: Conan-${{ hashFiles('src/**/conanfile.txt') }}-${{ matrix.platform }} - name: Generate tag id: tag @@ -54,7 +54,7 @@ jobs: echo "tag=$tag" >> $env:GITHUB_OUTPUT - name: Build - run: python publish.py ${{ matrix.platform }} + run: python publish.py ${{ matrix.platform }} unpackaged certs\Magpie.pfx "${{ secrets.MAGPIE_PFX_PASSWORD }}" env: MAJOR: ${{ inputs.major }} MINOR: ${{ inputs.minor }} diff --git a/Magpie.sln b/Magpie.sln index b93c7277e..06111b871 100644 --- a/Magpie.sln +++ b/Magpie.sln @@ -5,6 +5,7 @@ VisualStudioVersion = 17.1.32228.430 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Magpie", "src\Magpie\Magpie.vcxproj", "{1801171B-65B6-400F-92FF-73EAF499CFB3}" ProjectSection(ProjectDependencies) = postProject + {05B51BB8-08CB-4907-884F-8E2AD6BF6052} = {05B51BB8-08CB-4907-884F-8E2AD6BF6052} {1239537C-E5B8-427A-9E7F-EA443D1F3529} = {1239537C-E5B8-427A-9E7F-EA443D1F3529} {456CCAE4-2C51-4CF2-8D3A-1EFCE8C41A2D} = {456CCAE4-2C51-4CF2-8D3A-1EFCE8C41A2D} {62503530-B84B-4CC2-80B6-3F89618172B7} = {62503530-B84B-4CC2-80B6-3F89618172B7} @@ -23,9 +24,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution src\Common.Post.props = src\Common.Post.props src\Common.Pre.props = src\Common.Pre.props Directory.Build.props = Directory.Build.props - src\extract_winui_runtime.py = src\extract_winui_runtime.py src\HybridCRT.props = src\HybridCRT.props - src\WinUI.props = src\WinUI.props + src\WinUI.targets = src\WinUI.targets EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Shared", "src\Shared\Shared.vcxitems", "{4EB33017-68C1-40FE-877A-BCFAB2832F18}" @@ -48,6 +48,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Natvis", "Natvis", "{9808D3 EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Updater", "src\Updater\Updater.vcxproj", "{E82B7A20-0557-4DC1-B418-87977D7450A4}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TouchHelper", "src\TouchHelper\TouchHelper.vcxproj", "{05B51BB8-08CB-4907-884F-8E2AD6BF6052}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -104,6 +106,14 @@ Global {E82B7A20-0557-4DC1-B418-87977D7450A4}.Release|ARM64.Build.0 = Release|ARM64 {E82B7A20-0557-4DC1-B418-87977D7450A4}.Release|x64.ActiveCfg = Release|x64 {E82B7A20-0557-4DC1-B418-87977D7450A4}.Release|x64.Build.0 = Release|x64 + {05B51BB8-08CB-4907-884F-8E2AD6BF6052}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {05B51BB8-08CB-4907-884F-8E2AD6BF6052}.Debug|ARM64.Build.0 = Debug|ARM64 + {05B51BB8-08CB-4907-884F-8E2AD6BF6052}.Debug|x64.ActiveCfg = Debug|x64 + {05B51BB8-08CB-4907-884F-8E2AD6BF6052}.Debug|x64.Build.0 = Debug|x64 + {05B51BB8-08CB-4907-884F-8E2AD6BF6052}.Release|ARM64.ActiveCfg = Release|ARM64 + {05B51BB8-08CB-4907-884F-8E2AD6BF6052}.Release|ARM64.Build.0 = Release|ARM64 + {05B51BB8-08CB-4907-884F-8E2AD6BF6052}.Release|x64.ActiveCfg = Release|x64 + {05B51BB8-08CB-4907-884F-8E2AD6BF6052}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/README.md b/README.md index f5bfef661..8036d5a4d 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ -:earth_africa: **English** | [简体中文](./README_ZH.md) +🌍 **English** | [简体中文](./README_ZH.md) Magpie is a lightweight window scaling tool that comes equipped with various efficient scaling algorithms and filters. Its primary purpose is to enhance game graphics and enable non-fullscreen games to display in fullscreen mode. @@ -21,15 +21,15 @@ We are using [Weblate](https://weblate.org/) for localization work and would app [![Translation status](https://hosted.weblate.org/widgets/magpie/-/287x66-white.png)](https://hosted.weblate.org/engage/magpie/) -:point_right: [Download](https://github.com/Blinue/Magpie/releases) +👉 [Download](https://github.com/Blinue/Magpie/releases) -:point_right: [FAQ](https://github.com/Blinue/Magpie/wiki/FAQ%20(EN)) +👉 [FAQ](https://github.com/Blinue/Magpie/wiki/FAQ%20(EN)) -:point_right: [Built-in effects](https://github.com/Blinue/Magpie/wiki/Built-in%20effects) +👉 [Built-in effects](https://github.com/Blinue/Magpie/wiki/Built-in%20effects) -:point_right: [Compilation guide](https://github.com/Blinue/Magpie/wiki/Compilation%20guide) +👉 [Compilation guide](https://github.com/Blinue/Magpie/wiki/Compilation%20guide) -:point_right: [Contributing](./CONTRIBUTING.md) +👉 [Contributing](./CONTRIBUTING.md) ## Features diff --git a/README_ZH.md b/README_ZH.md index d6ef037cc..f8ffdf18d 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -21,15 +21,15 @@ Magpie 是一个轻量级的窗口缩放工具,内置了多种高效的缩放 [![翻译状态](https://hosted.weblate.org/widgets/magpie/-/287x66-white.png)](https://hosted.weblate.org/engage/magpie/) -:point_right: [下载](https://github.com/Blinue/Magpie/releases) +👉 [下载](https://github.com/Blinue/Magpie/releases) -:point_right: [FAQ](https://github.com/Blinue/Magpie/wiki/FAQ) +👉 [FAQ](https://github.com/Blinue/Magpie/wiki/FAQ) -:point_right: [内置效果介绍](https://github.com/Blinue/Magpie/wiki/内置效果介绍) +👉 [内置效果介绍](https://github.com/Blinue/Magpie/wiki/内置效果介绍) -:point_right: [编译指南](https://github.com/Blinue/Magpie/wiki/编译指南) +👉 [编译指南](https://github.com/Blinue/Magpie/wiki/编译指南) -:point_right: [贡献指南](./CONTRIBUTING_ZH.md) +👉 [贡献指南](./CONTRIBUTING_ZH.md) ## 功能 diff --git a/certs/.gitignore b/certs/.gitignore new file mode 100644 index 000000000..12621cbc9 --- /dev/null +++ b/certs/.gitignore @@ -0,0 +1 @@ +!*.pfx \ No newline at end of file diff --git a/certs/Magpie.pfx b/certs/Magpie.pfx new file mode 100644 index 000000000..d12c476b8 Binary files /dev/null and b/certs/Magpie.pfx differ diff --git a/docs/About touch support.md b/docs/About touch support.md new file mode 100644 index 000000000..de327099b --- /dev/null +++ b/docs/About touch support.md @@ -0,0 +1,13 @@ +Due to OS security restrictions, Magpie requires UIAccess privileges to support touch input. Obtaining this privilege necessitates meeting two conditions: + +1. The application must possess a digital signature, and this signature must be verified by a certificate associated with a trusted root certificate authority store on the local machine. +2. The application must reside in a "secure location", such as the Program Files or System32 folders. + +When enabling touch support, Magpie performs the following actions: + +1. Adds a self-signed certificate to the trusted root certificate authority store. +2. Copies the TouchHelper.exe to `System32\Magpie`. During scaling, Magpie runs this program to enable touch support. + +Both of these actions constitute significant changes to the OS, thus requiring administrator privileges. If touch support is no longer needed, this option should be disabled. Magpie will then revert these changes, leaving no traces in the OS. + +Touch support may fail for various reasons, such as when TouchHelper.exe requires an update. In such cases, Magpie will request administrator privileges before scaling to resolve the issue. diff --git a/docs/Compilation guide.md b/docs/Compilation guide.md index 640a580a1..a53a69147 100644 --- a/docs/Compilation guide.md +++ b/docs/Compilation guide.md @@ -20,7 +20,7 @@ In order to compile Magpie, you need to first install: conan --version ``` -### Compile +### Compiling 1. Clone the repo @@ -29,3 +29,17 @@ In order to compile Magpie, you need to first install: ``` 2. Open the Magpie.sln in the root directory and build the solution. + +### Enabling Touch Support + +To enable touch input support, TouchHelper.exe needs to be signed. While signing is automatically done in the CI pipeline, you can also manually sign it. Follow these steps: + +1. Create a self-signed certificate and export it as a pfx file. +2. Replace the `CERT_FINGERPRINT` constant in `src/Magpie/TouchHelper.cpp` with the SHA-1 hash (i.e., fingerprint) of your certificate. +3. Run the following command in the root directory of the repository: + +```bash +python publish.py x64 unpackaged +``` + +This will compile Magpie and sign TouchHelper.exe. The compiled files will be located in `publish\x64`. diff --git a/docs/Interact with Magpie programally.md b/docs/Interact with Magpie programally.md new file mode 100644 index 000000000..42d6f441d --- /dev/null +++ b/docs/Interact with Magpie programally.md @@ -0,0 +1,89 @@ +Magpie provides mechanisms for interaction with other programs. Through these mechanisms, your application can cooperate with Magpie. + +[MagpieWatcher](https://github.com/Blinue/MagpieWatcher) demonstrates how to use these mechanisms. + +## How to Receive Notifications When Scaling State Changes + +You should listen for the MagpieScalingChanged message. + +```c++ +UINT WM_MAGPIE_SCALINGCHANGED = RegisterWindowMessage(L"MagpieScalingChanged"); +``` + +### Parameters + +`wParam` is the event ID. For different events, `lParam` has different meanings. Currently, two events are supported: + +* 0: Scaling has ended. `lParam` is not used. +* 1: Scaling has started. `lParam` is the handle of the scaling window. + +### Notes + +If your process has a higher integrity level than Magpie, you won't receive messages broadcasted by Magpie due to User Interface Privilege Isolation (UIPI). In such cases, call [ChangeWindowMessageFilterEx](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-changewindowmessagefilterex) to allow receiving the MagpieScalingChanged message. + +```c++ +ChangeWindowMessageFilterEx(hYourWindow, WM_MAGPIE_SCALINGCHANGED, MSGFLT_ADD, nullptr); +``` + +## How to Get the Handle of the Scaling Window + +You can listen for the MagpieScalingChanged message to obtain the handle of the scaling window. Additionally, while Magpie is scaling, you can also search for the window with the class name `Window_Magpie_967EB565-6F73-4E94-AE53-00CC42592A22`. Magpie ensures that this class name remains unchanged and that only one scaling window exists at a time. + +```c++ +HWND hwndScaling = FindWindow(L"Window_Magpie_967EB565-6F73-4E94-AE53-00CC42592A22", nullptr); +``` + +## How to Place Your Window Above the Scaling Window + +Your window must be topmost. You should also listen for the MagpieScalingChanged message; you will receive one after the scaling window is shown, and then you can use `BringWindowToTop` to place your window above it. The scaling window does not attempt to adjust its position on the Z-axis while it exists. + +```c++ +HWND hWnd = CreateWindowEx(WS_EX_TOPMOST, ...); +... +if (message == WM_MAGPIE_SCALINGCHANGED) { + switch (wParam) { + case 0: + // Scaling has ended + break; + case 1: + // Scaling has started + // Place this window above the scaling window + BringWindowToTop(hWnd); + break; + default: + break; + } +} +``` + +## How to Obtain Scaling Information + +Scaling information is stored in the [window properties](https://learn.microsoft.com/en-us/windows/win32/winmsg/about-window-properties) of the scaling window. Currently available properties include: + +* `Magpie.SrcHWND`: Handle of the source window +* `Magpie.SrcLeft`、`Magpie.SrcTop`、`Magpie.SrcRight`、`Magpie.SrcBottom`: Source region of scaling +* `Magpie.DestLeft`、`Magpie.DestTop`、`Magpie.DestRight`、`Magpie.DestBottom`: Destination region of scaling + +```c++ +HWND hwndSrc = (HWND)GetProp(hwndScaling, L"Magpie.SrcHWND"); + +RECT srcRect; +srcRect.left = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.SrcLeft"); +srcRect.top = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.SrcTop"); +srcRect.right = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.SrcRight"); +srcRect.bottom = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.SrcBottom"); + +RECT destRect; +destRect.left = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.DestLeft"); +destRect.top = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.DestTop"); +destRect.right = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.DestRight"); +destRect.bottom = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.DestBottom"); +``` + +## How to Keep Magpie Scaling When Your Window Is in the Foreground + +Magpie stops scaling when the foreground window changes, with some system windows being exceptions. By setting the `Magpie.ToolWindow` property, you can include your window and all its owned windows in the exceptions list. + +```c++ +SetProp(hYourWindow, L"Magpie.ToolWindow", (HANDLE)TRUE); +``` diff --git "a/docs/\344\273\245\347\274\226\347\250\213\346\226\271\345\274\217\344\270\216 Magpie \344\272\244\344\272\222.md" "b/docs/\344\273\245\347\274\226\347\250\213\346\226\271\345\274\217\344\270\216 Magpie \344\272\244\344\272\222.md" new file mode 100644 index 000000000..5891f8117 --- /dev/null +++ "b/docs/\344\273\245\347\274\226\347\250\213\346\226\271\345\274\217\344\270\216 Magpie \344\272\244\344\272\222.md" @@ -0,0 +1,89 @@ +Magpie 提供了和其他程序交互的机制。通过它们,你的应用可以和 Magpie 配合使用。 + +[MagpieWatcher](https://github.com/Blinue/MagpieWatcher) 演示了如何使用这些机制。 + +## 如何在缩放状态改变时得到通知 + +你应该监听 MagpieScalingChanged 消息。 + +```c++ +UINT WM_MAGPIE_SCALINGCHANGED = RegisterWindowMessage(L"MagpieScalingChanged"); +``` + +### 参数 + +`wParam` 为事件 ID,对于不同的事件 `lParam` 有不同的含义。目前支持两个事件: + +* 0: 缩放已结束。不使用 `lParam`。 +* 1: 缩放已开始。`lParam` 为缩放窗口句柄。 + +### 注意事项 + +如果你的进程完整性级别 (Integration level) 比 Magpie 更高,由于用户界面特权隔离 (UIPI),你将无法收到 Magpie 广播的消息。这种情况下请调用 [ChangeWindowMessageFilterEx](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-changewindowmessagefilterex) 以允许接收 MagpieScalingChanged 消息。 + +```c++ +ChangeWindowMessageFilterEx(hYourWindow, WM_MAGPIE_SCALINGCHANGED, MSGFLT_ADD, nullptr); +``` + +## 如何获取缩放窗口句柄 + +你可以监听 MagpieScalingChanged 消息来获取缩放窗口句柄,也可以查找类名为`Window_Magpie_967EB565-6F73-4E94-AE53-00CC42592A22`的窗口以在缩放中途获取该句柄。Magpie 将确保此类名不会改变,且不会同时存在多个缩放窗口。 + +```c++ +HWND hwndScaling = FindWindow(L"Window_Magpie_967EB565-6F73-4E94-AE53-00CC42592A22", nullptr); +``` + +## 如何将你的窗口置于缩放窗口上方 + +你的窗口必须是置顶的。你还应该监听 MagpieScalingChanged 消息,当收到该消息时缩放窗口已经显示,然后你可以使用 `BringWindowToTop` 函数将自己的窗口置于缩放窗口上方。缩放窗口在存在期间不会尝试调整自己在 Z 轴的位置。 + +```c++ +HWND hWnd = CreateWindowEx(WS_EX_TOPMOST, ...); +... +if (message == WM_MAGPIE_SCALINGCHANGED) { + switch (wParam) { + case 0: + // 缩放已结束 + break; + case 1: + // 缩放已开始 + // 将本窗口置于缩放窗口上面 + BringWindowToTop(hWnd); + break; + default: + break; + } +} +``` + +## 如何获取缩放信息 + +缩放窗口的[窗口属性](https://learn.microsoft.com/en-us/windows/win32/winmsg/about-window-properties)中存储着缩放信息。目前支持以下属性: + +* `Magpie.SrcHWND`: 源窗口句柄 +* `Magpie.SrcLeft`、`Magpie.SrcTop`、`Magpie.SrcRight`、`Magpie.SrcBottom`: 被缩放区域的边界 +* `Magpie.DestLeft`、`Magpie.DestTop`、`Magpie.DestRight`、`Magpie.DestBottom`: 缩放后区域矩形边界 + +```c++ +HWND hwndSrc = (HWND)GetProp(hwndScaling, L"Magpie.SrcHWND"); + +RECT srcRect; +srcRect.left = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.SrcLeft"); +srcRect.top = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.SrcTop"); +srcRect.right = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.SrcRight"); +srcRect.bottom = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.SrcBottom"); + +RECT destRect; +destRect.left = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.DestLeft"); +destRect.top = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.DestTop"); +destRect.right = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.DestRight"); +destRect.bottom = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.DestBottom"); +``` + +## 如何使 Magpie 在你的窗口位于前台时保持缩放 + +前台窗口改变时 Magpie 会停止缩放,只对某些系统窗口例外。你可以通过设置属性 `Magpie.ToolWindow` 将自己的窗口添加入例外,这对由该窗口拥有 (owned) 的窗口也有效。 + +```c++ +SetProp(hYourWindow, L"Magpie.ToolWindow", (HANDLE)TRUE); +``` diff --git "a/docs/\345\205\263\344\272\216\350\247\246\346\216\247\346\224\257\346\214\201.md" "b/docs/\345\205\263\344\272\216\350\247\246\346\216\247\346\224\257\346\214\201.md" new file mode 100644 index 000000000..f4a511547 --- /dev/null +++ "b/docs/\345\205\263\344\272\216\350\247\246\346\216\247\346\224\257\346\214\201.md" @@ -0,0 +1,13 @@ +由于操作系统的安全限制,Magpie 需要 UIAccess 权限来支持触控输入。获得此权限需要满足两个条件: + +1. 应用程序必须有一个数字签名,且该签名能够通过与本地计算机上受信任的根证书颁发机构存储相关联的证书来验证。 +2. 应用程序必须位于“安全位置”,例如 Program Files、System32 等文件夹中。 + +启用触控支持时,Magpie 将进行以下操作: + +1. 向受信任的根证书颁发机构存储添加自签名证书。 +2. 将 TouchHelper.exe 文件复制到 `System32\Magpie` 文件夹。在缩放时,Magpie 会运行此程序以实现触控支持。 + +这两项操作都对操作系统构成重大更改,因此需要管理员权限。如果不再需要触控支持,应关闭此选项,Magpie 会撤销这些更改,不在操作系统中留下痕迹。 + +触控支持可能会因为某些原因失效(比如 TouchHelper.exe 需要更新时),如果发生这种情况,Magpie 会在缩放前请求管理员权限来修复它。 diff --git "a/docs/\347\274\226\350\257\221\346\214\207\345\215\227.md" "b/docs/\347\274\226\350\257\221\346\214\207\345\215\227.md" index b65467c54..bad688260 100644 --- "a/docs/\347\274\226\350\257\221\346\214\207\345\215\227.md" +++ "b/docs/\347\274\226\350\257\221\346\214\207\345\215\227.md" @@ -29,3 +29,17 @@ ``` 2. 打开根目录的 Magpie.sln 然后生成解决方案。 + +## 启用触控支持 + +为了支持触控输入,TouchHelper.exe 应签名。签名是在 CI 中自动进行的,但你也可以手动签名,请执行以下步骤: + +1. 创建自签名证书,将其导出为 pfx。 +2. 将 `src/Magpie/TouchHelper.cpp` 中的 `CERT_FINGERPRINT` 常量替换为你的证书的 SHA-1 哈希值(也即该证书的指纹)。 +3. 在存储库根目录下执行以下命令: + +```bash +python publish.py x64 unpackaged +``` + +这将编译 Magpie 并为 TouchHelper.exe 签名。编译出的程序位于 `publish\x64`。 diff --git a/publish.py b/publish.py index 64ad05526..28ebc61a0 100644 --- a/publish.py +++ b/publish.py @@ -19,7 +19,7 @@ pass platform = "x64" -if len(sys.argv) == 2: +if len(sys.argv) >= 2: platform = sys.argv[1] if not platform in ["x64", "ARM64"]: raise Exception("非法参数") @@ -60,7 +60,7 @@ os.chdir(os.path.dirname(__file__)) p = subprocess.run("git rev-parse --short HEAD", capture_output=True) -commit_id = str(p.stdout, encoding="utf-8")[0:-1] +commitId = str(p.stdout, encoding="utf-8")[0:-1] if majorVersion != None: version_props = f";MajorVersion={majorVersion};MinorVersion={minorVersion};PatchVersion={patchVersion};VersionTag={tag}" @@ -98,7 +98,7 @@ version_props = "" p = subprocess.run( - f'"{msbuildPath}" -restore -p:RestorePackagesConfig=true;Configuration=Release;Platform={platform};OutDir={os.getcwd()}\\publish\\{platform}\\;CommitId={commit_id}{version_props} Magpie.sln' + f'"{msbuildPath}" -restore -p:RestorePackagesConfig=true;Configuration=Release;Platform={platform};OutDir={os.getcwd()}\\publish\\{platform}\\;CommitId={commitId}{version_props} Magpie.sln' ) if p.returncode != 0: raise Exception("编译失败") @@ -211,3 +211,18 @@ def remove_file(file): os.remove("priconfig.xml") print("已修剪 resources.pri", flush=True) + +##################################################################### +# +# 为 TouchHelper 签名 +# +##################################################################### + +if len(sys.argv) >= 5: + # sys.argv[2] 保留为打包选项 + pfxPath = os.path.join("..\..", sys.argv[3]) + pfxPassword = sys.argv[4] + + p = subprocess.run(f'"{windowsSdkDir}\\x64\\signtool.exe" sign /fd SHA256 /a /f "{pfxPath}" /p "{pfxPassword}" TouchHelper.exe') + if p.returncode != 0: + raise Exception("makepri 失败") diff --git a/src/Common.Post.props b/src/Common.Post.props index 6fffab5d8..105d4a64a 100644 --- a/src/Common.Post.props +++ b/src/Common.Post.props @@ -8,6 +8,7 @@ low $(SolutionDir)obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(IntDir)Generated Files\ + $(Configuration.EndsWith('Packaged')) @@ -28,7 +29,7 @@ Use pch.h $(IntDir)pch.pch - _WINDOWS;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;WINRT_NO_MODULE_LOCK;NOGDICAPMASKS;NOICONS;NOATOM;NOCLIPBOARD;NODRAWTEXT;NOMEMMGR;NOMETAFILE;NOMINMAX;NOOPENFILE;NOSCROLL;NOSERVICE;NOSOUND;NOTEXTMETRIC;NOCOMM;NOKANJI;NOHELP;NOPROFILER;NODEFERWINDOWPOS;NOMCX;%(PreprocessorDefinitions) + _WINDOWS;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;WINRT_NO_MODULE_LOCK;WIL_SUPPRESS_EXCEPTIONS;NOGDICAPMASKS;NOICONS;NOATOM;NOCLIPBOARD;NODRAWTEXT;NOMEMMGR;NOMETAFILE;NOMINMAX;NOOPENFILE;NOSCROLL;NOSERVICE;NOSOUND;NOTEXTMETRIC;NOCOMM;NOKANJI;NOHELP;NOPROFILER;NODEFERWINDOWPOS;NOMCX;%(PreprocessorDefinitions) MAGPIE_COMMIT_ID=$(CommitId);%(PreprocessorDefinitions) MAGPIE_VERSION_MAJOR=$(MajorVersion);MAGPIE_VERSION_MINOR=$(MinorVersion);MAGPIE_VERSION_PATCH=$(PatchVersion);MAGPIE_VERSION_TAG=$(VersionTag);%(PreprocessorDefinitions) /bigobj %(AdditionalOptions) diff --git a/src/Effects/SMAA/SMAA.hlsli b/src/Effects/SMAA/SMAA.hlsli index 9b4f06cb8..a6ea67787 100644 --- a/src/Effects/SMAA/SMAA.hlsli +++ b/src/Effects/SMAA/SMAA.hlsli @@ -1,6 +1,6 @@ // SMAA For Magpie // 移植自 https://github.com/iryoku/smaa -// 根据 Magpie 的需求做了一些更改: +// 根据 Magpie 的需求做了一些更改: // 1. 将 VS 的计算移到 PS 中 // 2. 删除一些用不到的功能,如 predicated thresholding,temporal supersampling 等 // 3. 添加两个采样器的预定义 SMAA_LINEAR_SAMPLER 和 SMAA_POINT_SAMPLER diff --git a/src/Magpie.App/AboutPage.xaml b/src/Magpie.App/AboutPage.xaml index 003b26b96..00ba67b7f 100644 --- a/src/Magpie.App/AboutPage.xaml +++ b/src/Magpie.App/AboutPage.xaml @@ -54,17 +54,14 @@ x:Load="{x:Bind ViewModel.IsAnyUpdateStatus, Mode=OneWay}"> + Severity="Error" /> + Severity="Success" /> + Severity="Informational"> fire_and_forget { - wchar_t exePath[MAX_PATH]; - GetModuleFileName(NULL, exePath, MAX_PATH); - auto weakThis = that->get_weak(); SoftwareBitmapSource bitmap; - co_await bitmap.SetBitmapAsync(IconHelper::ExtractIconFromExe(exePath, 256, USER_DEFAULT_SCREEN_DPI)); + co_await bitmap.SetBitmapAsync(IconHelper::ExtractIconFromExe( + Win32Utils::GetExePath().c_str(), 256, USER_DEFAULT_SCREEN_DPI)); if (!weakThis.get()) { co_return; } that->_logo = std::move(bitmap); - that->_propertyChangedEvent(*that, PropertyChangedEventArgs(L"Logo")); + that->RaisePropertyChanged(L"Logo"); })(this); } @@ -101,7 +99,7 @@ bool AboutViewModel::IsCheckForPreviewUpdates() const noexcept { void AboutViewModel::IsCheckForPreviewUpdates(bool value) { AppSettings::Get().IsCheckForPreviewUpdates(value); - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"IsCheckForPreviewUpdates")); + RaisePropertyChanged(L"IsCheckForPreviewUpdates"); } bool AboutViewModel::IsCheckForUpdatesButtonEnabled() const noexcept { @@ -119,7 +117,7 @@ bool AboutViewModel::IsAutoCheckForUpdates() const noexcept { void AboutViewModel::IsAutoCheckForUpdates(bool value) { AppSettings::Get().IsAutoCheckForUpdates(value); - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"IsAutoCheckForUpdates")); + RaisePropertyChanged(L"IsAutoCheckForUpdates"); } bool AboutViewModel::IsAnyUpdateStatus() const noexcept { @@ -142,7 +140,7 @@ void AboutViewModel::IsErrorWhileChecking(bool value) { } } - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"IsErrorWhileChecking")); + RaisePropertyChanged(L"IsErrorWhileChecking"); } bool AboutViewModel::IsNoUpdate() const noexcept { @@ -191,7 +189,7 @@ void AboutViewModel::IsUpdateCardOpen(bool value) { } } - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"IsUpdateCardOpen")); + RaisePropertyChanged(L"IsUpdateCardOpen"); } bool AboutViewModel::IsUpdateCardClosable() const noexcept { @@ -261,23 +259,23 @@ void AboutViewModel::Retry() { } void AboutViewModel::_UpdateService_StatusChanged(UpdateStatus status) { - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"IsCheckingForUpdates")); - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"IsCheckForUpdatesButtonEnabled")); - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"IsAnyUpdateStatus")); - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"IsErrorWhileChecking")); - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"IsNoUpdate")); - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"IsAvailable")); - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"IsDownloading")); - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"IsErrorWhileDownloading")); - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"IsInstalling")); - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"IsDownloadingOrLater")); - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"IsUpdateCardOpen")); - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"IsUpdateCardClosable")); - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"IsCancelButtonVisible")); + RaisePropertyChanged(L"IsCheckingForUpdates"); + RaisePropertyChanged(L"IsCheckForUpdatesButtonEnabled"); + RaisePropertyChanged(L"IsAnyUpdateStatus"); + RaisePropertyChanged(L"IsErrorWhileChecking"); + RaisePropertyChanged(L"IsNoUpdate"); + RaisePropertyChanged(L"IsAvailable"); + RaisePropertyChanged(L"IsDownloading"); + RaisePropertyChanged(L"IsErrorWhileDownloading"); + RaisePropertyChanged(L"IsInstalling"); + RaisePropertyChanged(L"IsDownloadingOrLater"); + RaisePropertyChanged(L"IsUpdateCardOpen"); + RaisePropertyChanged(L"IsUpdateCardClosable"); + RaisePropertyChanged(L"IsCancelButtonVisible"); if (status >= UpdateStatus::Available) { - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"UpdateCardTitle")); - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"UpdateReleaseNotesLink")); + RaisePropertyChanged(L"UpdateCardTitle"); + RaisePropertyChanged(L"UpdateReleaseNotesLink"); if (status == UpdateStatus::Downloading) { _downloadProgressChangedRevoker = UpdateService::Get().DownloadProgressChanged( @@ -288,15 +286,15 @@ void AboutViewModel::_UpdateService_StatusChanged(UpdateStatus status) { _downloadProgressChangedRevoker.Revoke(); if (status >= UpdateStatus::ErrorWhileDownloading) { - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"IsNoDownloadProgress")); + RaisePropertyChanged(L"IsNoDownloadProgress"); } } } } void AboutViewModel::_UpdateService_DownloadProgressChanged(double) { - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"IsNoDownloadProgress")); - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"DownloadProgress")); + RaisePropertyChanged(L"IsNoDownloadProgress"); + RaisePropertyChanged(L"DownloadProgress"); } } diff --git a/src/Magpie.App/AboutViewModel.h b/src/Magpie.App/AboutViewModel.h index 21de65c1b..a8680995f 100644 --- a/src/Magpie.App/AboutViewModel.h +++ b/src/Magpie.App/AboutViewModel.h @@ -4,17 +4,10 @@ namespace winrt::Magpie::App::implementation { -struct AboutViewModel : AboutViewModelT { +struct AboutViewModel : AboutViewModelT, + wil::notify_property_changed_base { AboutViewModel(); - event_token PropertyChanged(PropertyChangedEventHandler const& handler) { - return _propertyChangedEvent.add(handler); - } - - void PropertyChanged(event_token const& token) noexcept { - _propertyChangedEvent.remove(token); - } - Imaging::SoftwareBitmapSource Logo() const noexcept { return _logo; } @@ -73,7 +66,6 @@ struct AboutViewModel : AboutViewModelT { void _UpdateService_StatusChanged(UpdateStatus status); void _UpdateService_DownloadProgressChanged(double); - event _propertyChangedEvent; WinRTUtils::EventRevoker _updateStatusChangedRevoker; WinRTUtils::EventRevoker _downloadProgressChangedRevoker; WinRTUtils::EventRevoker _showOnHomePageChangedRevoker; diff --git a/src/Magpie.App/App.idl b/src/Magpie.App/App.idl index 9df1310f0..60edfbdbb 100644 --- a/src/Magpie.App/App.idl +++ b/src/Magpie.App/App.idl @@ -25,7 +25,7 @@ namespace Magpie.App { #include "EffectParametersViewModel.idl" #include "ScalingModeEffectItem.idl" #include "ScalingModeItem.idl" -#include "ScalingConfigurationViewModel.idl" +#include "ScalingModesViewModel.idl" #include "ProfileViewModel.idl" #include "SettingsViewModel.idl" #include "CandidateWindowItem.idl" @@ -34,7 +34,7 @@ namespace Magpie.App { #include "RootPage.idl" #include "AboutPage.idl" #include "HomePage.idl" -#include "ScalingConfigurationPage.idl" +#include "ScalingModesPage.idl" #include "ProfilePage.idl" #include "SettingsPage.idl" diff --git a/src/Magpie.App/App.xaml b/src/Magpie.App/App.xaml index 4915ea450..ee00a8160 100644 --- a/src/Magpie.App/App.xaml +++ b/src/Magpie.App/App.xaml @@ -52,28 +52,6 @@ Color="#FFFFFF" /> - - - - - - - 1 - - - - - - - 1 - - diff --git a/src/Magpie.App/AppSettings.cpp b/src/Magpie.App/AppSettings.cpp index f29fb97f8..8eb94b250 100644 --- a/src/Magpie.App/AppSettings.cpp +++ b/src/Magpie.App/AppSettings.cpp @@ -148,6 +148,7 @@ static HRESULT CALLBACK TaskDialogCallback( ) { if (msg == TDN_CREATED) { // 将任务栏图标替换为 Magpie 的图标 + // GetModuleHandle 获取 exe 文件的句柄 HINSTANCE hInst = GetModuleHandle(nullptr); ReplaceIcon(hInst, hWnd, true); ReplaceIcon(hInst, hWnd, false); @@ -166,17 +167,18 @@ static void ShowErrorMessage(const wchar_t* mainInstruction, const wchar_t* cont const hstring errorStr = resourceLoader.GetString(L"AppSettings_Dialog_Error"); const hstring exitStr = resourceLoader.GetString(L"AppSettings_Dialog_Exit"); - TASKDIALOGCONFIG tdc{ sizeof(TASKDIALOGCONFIG) }; - tdc.dwFlags = TDF_SIZE_TO_CONTENT; - tdc.pszWindowTitle = errorStr.c_str(); - tdc.pszMainIcon = TD_ERROR_ICON; - tdc.pszMainInstruction = mainInstruction; - tdc.pszContent = content; - tdc.pfCallback = TaskDialogCallback; - tdc.cButtons = 1; - TASKDIALOG_BUTTON button{ IDCANCEL, exitStr.c_str()}; - tdc.pButtons = &button; - + TASKDIALOG_BUTTON button{ IDCANCEL, exitStr.c_str() }; + TASKDIALOGCONFIG tdc{ + .cbSize = sizeof(TASKDIALOGCONFIG), + .dwFlags = TDF_SIZE_TO_CONTENT, + .pszWindowTitle = errorStr.c_str(), + .pszMainIcon = TD_ERROR_ICON, + .pszMainInstruction = mainInstruction, + .pszContent = content, + .cButtons = 1, + .pButtons = &button, + .pfCallback = TaskDialogCallback + }; TaskDialogIndirect(&tdc, nullptr, nullptr, nullptr); } @@ -186,21 +188,25 @@ static bool ShowOkCancelWarningMessage( const wchar_t* okText, const wchar_t* cancelText ) noexcept { - TASKDIALOGCONFIG tdc{ sizeof(TASKDIALOGCONFIG) }; - tdc.dwFlags = TDF_SIZE_TO_CONTENT; const hstring warningStr = ResourceLoader::GetForCurrentView(CommonSharedConstants::APP_RESOURCE_MAP_ID) .GetString(L"AppSettings_Dialog_Warning"); - tdc.pszWindowTitle = warningStr.c_str(); - tdc.pszMainIcon = TD_WARNING_ICON; - tdc.pszMainInstruction = mainInstruction; - tdc.pszContent = content; - tdc.pfCallback = TaskDialogCallback; + TASKDIALOG_BUTTON buttons[]{ {IDOK, okText}, {IDCANCEL, cancelText} }; - tdc.cButtons = (UINT)std::size(buttons); - tdc.pButtons = buttons; + + TASKDIALOGCONFIG tdc{ + .cbSize = sizeof(TASKDIALOGCONFIG), + .dwFlags = TDF_SIZE_TO_CONTENT, + .pszWindowTitle = warningStr.c_str(), + .pszMainIcon = TD_WARNING_ICON, + .pszMainInstruction = mainInstruction, + .pszContent = content, + .cButtons = (UINT)std::size(buttons), + .pButtons = buttons, + .pfCallback = TaskDialogCallback + }; int button = 0; TaskDialogIndirect(&tdc, &button, nullptr, nullptr); @@ -217,9 +223,12 @@ bool AppSettings::Initialize() noexcept { CommonSharedConstants::CONFIG_DIR, CommonSharedConstants::CONFIG_FILENAME).c_str()); std::wstring existingConfigPath; - _UpdateConfigPath(&existingConfigPath); + if (!_UpdateConfigPath(&existingConfigPath)) { + logger.Error("_UpdateConfigPath 失败"); + return false; + } - logger.Info(StrUtils::Concat("便携模式:", _isPortableMode ? "是" : "否")); + logger.Info(StrUtils::Concat("便携模式: ", _isPortableMode ? "是" : "否")); if (existingConfigPath.empty()) { logger.Info("不存在配置文件"); @@ -254,7 +263,7 @@ bool AppSettings::Initialize() noexcept { rapidjson::Document doc; doc.ParseInsitu(configText.data()); if (doc.HasParseError()) { - Logger::Get().Error(fmt::format("解析配置失败\n\t错误码:{}", (int)doc.GetParseError())); + Logger::Get().Error(fmt::format("解析配置失败\n\t错误码: {}", (int)doc.GetParseError())); ResourceLoader resourceLoader = ResourceLoader::GetForCurrentView(CommonSharedConstants::APP_RESOURCE_MAP_ID); hstring title = resourceLoader.GetString(L"AppSettings_ErrorDialog_NotValidJson"); @@ -307,16 +316,23 @@ void AppSettings::IsPortableMode(bool value) noexcept { if (!value) { // 关闭便携模式需删除本地配置文件 - // 不关心是否成功 - DeleteFile(StrUtils::Concat(_configDir, CommonSharedConstants::CONFIG_FILENAME).c_str()); + if (!DeleteFile(StrUtils::Concat(_configDir, CommonSharedConstants::CONFIG_FILENAME).c_str())) { + if (GetLastError() != ERROR_FILE_NOT_FOUND) { + Logger::Get().Win32Error("删除本地配置文件失败"); + return; + } + } } - Logger::Get().Info(value ? "已开启便携模式" : "已关闭便携模式"); - _isPortableMode = value; - _UpdateConfigPath(); - SaveAsync(); + if (_UpdateConfigPath()) { + Logger::Get().Info(value ? "已开启便携模式" : "已关闭便携模式"); + SaveAsync(); + } else { + Logger::Get().Error(value ? "开启便携模式失败" : "关闭便携模式失败"); + _isPortableMode = !value; + } } void AppSettings::Language(int value) { @@ -334,7 +350,7 @@ void AppSettings::Theme(Magpie::App::Theme value) { } _theme = value; - _themeChangedEvent(value); + ThemeChanged.Invoke(value); SaveAsync(); } @@ -346,7 +362,7 @@ void AppSettings::SetShortcut(ShortcutAction action, const Magpie::App::Shortcut _shortcuts[(size_t)action] = value; Logger::Get().Info(fmt::format("热键 {} 已更改为 {}", ShortcutHelper::ToString(action), StrUtils::UTF16ToUTF8(value.ToString()))); - _shortcutChangedEvent(action); + ShortcutChanged.Invoke(action); SaveAsync(); } @@ -357,7 +373,7 @@ void AppSettings::IsAutoRestore(bool value) noexcept { } _isAutoRestore = value; - _isAutoRestoreChangedEvent(value); + IsAutoRestoreChanged.Invoke(value); SaveAsync(); } @@ -368,7 +384,7 @@ void AppSettings::CountdownSeconds(uint32_t value) noexcept { } _countdownSeconds = value; - _countdownSecondsChangedEvent(value); + CountdownSecondsChanged.Invoke(value); SaveAsync(); } @@ -410,7 +426,7 @@ void AppSettings::IsShowNotifyIcon(bool value) noexcept { } _isShowNotifyIcon = value; - _isShowNotifyIconChangedEvent(value); + IsShowNotifyIconChanged.Invoke(value); SaveAsync(); } @@ -442,8 +458,9 @@ void AppSettings::_UpdateWindowPlacement() noexcept { } bool AppSettings::_Save(const _AppSettingsData& data) noexcept { - if (!Win32Utils::CreateDir(data._configDir)) { - Logger::Get().Error("创建配置文件夹失败"); + HRESULT hr = wil::CreateDirectoryDeepNoThrow(data._configDir.c_str()); + if (FAILED(hr)) { + Logger::Get().ComError("创建配置文件夹失败", hr); return false; } @@ -534,7 +551,7 @@ bool AppSettings::_Save(const _AppSettingsData& data) noexcept { writer.EndObject(); // 防止并行写入 - std::scoped_lock lk(_saveMutex); + auto lock = _saveLock.lock_exclusive(); if (!Win32Utils::WriteTextFile(data._configPath.c_str(), { json.GetString(), json.GetLength() })) { Logger::Get().Error("保存配置失败"); return false; @@ -801,16 +818,16 @@ bool AppSettings::_LoadProfile( profile.maxFrameRate = 60.0f; } - JsonHelper::ReadBoolFlag(profileObj, "disableWindowResizing", ScalingFlags::DisableWindowResizing, profile.flags); - JsonHelper::ReadBoolFlag(profileObj, "3DGameMode", ScalingFlags::Is3DGameMode, profile.flags); - JsonHelper::ReadBoolFlag(profileObj, "showFPS", ScalingFlags::ShowFPS, profile.flags); - if (!JsonHelper::ReadBoolFlag(profileObj, "captureTitleBar", ScalingFlags::CaptureTitleBar, profile.flags, true)) { + JsonHelper::ReadBoolFlag(profileObj, "disableWindowResizing", ScalingFlags::DisableWindowResizing, profile.scalingFlags); + JsonHelper::ReadBoolFlag(profileObj, "3DGameMode", ScalingFlags::Is3DGameMode, profile.scalingFlags); + JsonHelper::ReadBoolFlag(profileObj, "showFPS", ScalingFlags::ShowFPS, profile.scalingFlags); + if (!JsonHelper::ReadBoolFlag(profileObj, "captureTitleBar", ScalingFlags::CaptureTitleBar, profile.scalingFlags, true)) { // v0.10.0-preview1 使用 reserveTitleBar - JsonHelper::ReadBoolFlag(profileObj, "reserveTitleBar", ScalingFlags::CaptureTitleBar, profile.flags); + JsonHelper::ReadBoolFlag(profileObj, "reserveTitleBar", ScalingFlags::CaptureTitleBar, profile.scalingFlags); } - JsonHelper::ReadBoolFlag(profileObj, "adjustCursorSpeed", ScalingFlags::AdjustCursorSpeed, profile.flags); - JsonHelper::ReadBoolFlag(profileObj, "drawCursor", ScalingFlags::DrawCursor, profile.flags); - JsonHelper::ReadBoolFlag(profileObj, "disableDirectFlip", ScalingFlags::DisableDirectFlip, profile.flags); + JsonHelper::ReadBoolFlag(profileObj, "adjustCursorSpeed", ScalingFlags::AdjustCursorSpeed, profile.scalingFlags); + JsonHelper::ReadBoolFlag(profileObj, "drawCursor", ScalingFlags::DrawCursor, profile.scalingFlags); + JsonHelper::ReadBoolFlag(profileObj, "disableDirectFlip", ScalingFlags::DisableDirectFlip, profile.scalingFlags); { uint32_t cursorScaling = (uint32_t)CursorScaling::NoScaling; @@ -983,12 +1000,14 @@ static std::wstring FindOldConfig(const wchar_t* localAppDataDir) noexcept { return {}; } -void AppSettings::_UpdateConfigPath(std::wstring* existingConfigPath) noexcept { +bool AppSettings::_UpdateConfigPath(std::wstring* existingConfigPath) noexcept { if (_isPortableMode) { - wchar_t curDir[MAX_PATH]; - GetCurrentDirectory(MAX_PATH, curDir); + HRESULT hr = wil::GetFullPathNameW(CommonSharedConstants::CONFIG_DIR, _configDir); + if (FAILED(hr)) { + Logger::Get().ComError("GetFullPathNameW 失败", hr); + return false; + } - _configDir = StrUtils::Concat(curDir, L"\\", CommonSharedConstants::CONFIG_DIR); _configPath = _configDir + CommonSharedConstants::CONFIG_FILENAME; if (existingConfigPath) { @@ -997,37 +1016,36 @@ void AppSettings::_UpdateConfigPath(std::wstring* existingConfigPath) noexcept { } } } else { - wchar_t localAppDataDir[MAX_PATH]; - HRESULT hr = SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, localAppDataDir); - if (SUCCEEDED(hr)) { - _configDir = fmt::format(L"{}\\Magpie\\{}v{}\\", - localAppDataDir, CommonSharedConstants::CONFIG_DIR, CONFIG_VERSION); - _configPath = _configDir + CommonSharedConstants::CONFIG_FILENAME; - - if (existingConfigPath) { - if (Win32Utils::FileExists(_configPath.c_str())) { - *existingConfigPath = _configPath; - } else { - // 查找旧版本配置文件 - *existingConfigPath = FindOldConfig(localAppDataDir); - } - } - } else { - Logger::Get().ComError("SHGetFolderPath 失败", hr); + wil::unique_cotaskmem_string localAppDataDir; + HRESULT hr = SHGetKnownFolderPath( + FOLDERID_LocalAppData, KF_FLAG_DEFAULT, NULL, localAppDataDir.put()); + if (FAILED(hr)) { + Logger::Get().ComError("SHGetKnownFolderPath 失败", hr); + return false; + } - _configDir = CommonSharedConstants::CONFIG_DIR; - _configPath = _configDir + CommonSharedConstants::CONFIG_FILENAME; + _configDir = fmt::format(L"{}\\Magpie\\{}v{}\\", + localAppDataDir.get(), CommonSharedConstants::CONFIG_DIR, CONFIG_VERSION); + _configPath = _configDir + CommonSharedConstants::CONFIG_FILENAME; - if (existingConfigPath) { - if (Win32Utils::FileExists(_configPath.c_str())) { - *existingConfigPath = _configPath; - } + if (existingConfigPath) { + if (Win32Utils::FileExists(_configPath.c_str())) { + *existingConfigPath = _configPath; + } else { + // 查找旧版本配置文件 + *existingConfigPath = FindOldConfig(localAppDataDir.get()); } } } - // 确保 ConfigDir 存在 - Win32Utils::CreateDir(_configDir.c_str(), true); + // 确保配置文件夹存在 + HRESULT hr = wil::CreateDirectoryDeepNoThrow(_configDir.c_str()); + if (FAILED(hr)) { + Logger::Get().ComError("创建配置文件夹失败", hr); + return false; + } + + return true; } } diff --git a/src/Magpie.App/AppSettings.h b/src/Magpie.App/AppSettings.h index d078bc45f..c05a982f4 100644 --- a/src/Magpie.App/AppSettings.h +++ b/src/Magpie.App/AppSettings.h @@ -106,21 +106,6 @@ class AppSettings : private _AppSettingsData { } void Theme(Magpie::App::Theme value); - event_token ThemeChanged(delegate const& handler) { - return _themeChangedEvent.add(handler); - } - - WinRTUtils::EventRevoker ThemeChanged(auto_revoke_t, delegate const& handler) { - event_token token = ThemeChanged(handler); - return WinRTUtils::EventRevoker([this, token]() { - ThemeChanged(token); - }); - } - - void ThemeChanged(event_token const& token) { - _themeChangedEvent.remove(token); - } - Point MainWindowCenter() const noexcept { return _mainWindowCenter; } @@ -139,63 +124,18 @@ class AppSettings : private _AppSettingsData { void SetShortcut(ShortcutAction action, const Shortcut& value); - event_token ShortcutChanged(delegate const& handler) { - return _shortcutChangedEvent.add(handler); - } - - WinRTUtils::EventRevoker ShortcutChanged(auto_revoke_t, delegate const& handler) { - event_token token = ShortcutChanged(handler); - return WinRTUtils::EventRevoker([this, token]() { - ShortcutChanged(token); - }); - } - - void ShortcutChanged(event_token const& token) { - _shortcutChangedEvent.remove(token); - } - bool IsAutoRestore() const noexcept { return _isAutoRestore; } void IsAutoRestore(bool value) noexcept; - event_token IsAutoRestoreChanged(delegate const& handler) { - return _isAutoRestoreChangedEvent.add(handler); - } - - WinRTUtils::EventRevoker IsAutoRestoreChanged(auto_revoke_t, delegate const& handler) { - event_token token = IsAutoRestoreChanged(handler); - return WinRTUtils::EventRevoker([this, token]() { - IsAutoRestoreChanged(token); - }); - } - - void IsAutoRestoreChanged(event_token const& token) { - _isAutoRestoreChangedEvent.remove(token); - } - uint32_t CountdownSeconds() const noexcept { return _countdownSeconds; } void CountdownSeconds(uint32_t value) noexcept; - event_token CountdownSecondsChanged(delegate const& handler) { - return _countdownSecondsChangedEvent.add(handler); - } - - WinRTUtils::EventRevoker CountdownSecondsChanged(auto_revoke_t, delegate const& handler) { - event_token token = CountdownSecondsChanged(handler); - return WinRTUtils::EventRevoker([this, token]() { - CountdownSecondsChanged(token); - }); - } - - void CountdownSecondsChanged(event_token const& token) { - _countdownSecondsChangedEvent.remove(token); - } - bool IsDeveloperMode() const noexcept { return _isDeveloperMode; } @@ -285,21 +225,6 @@ class AppSettings : private _AppSettingsData { void IsShowNotifyIcon(bool value) noexcept; - event_token IsShowNotifyIconChanged(delegate const& handler) { - return _isShowNotifyIconChangedEvent.add(handler); - } - - WinRTUtils::EventRevoker IsShowNotifyIconChanged(auto_revoke_t, delegate const& handler) { - event_token token = IsShowNotifyIconChanged(handler); - return WinRTUtils::EventRevoker([this, token]() { - IsShowNotifyIconChanged(token); - }); - } - - void IsShowNotifyIconChanged(event_token const& token) { - _isShowNotifyIconChangedEvent.remove(token); - } - bool IsInlineParams() const noexcept { return _isInlineParams; } @@ -319,25 +244,10 @@ class AppSettings : private _AppSettingsData { void IsAutoCheckForUpdates(bool value) noexcept { _isAutoCheckForUpdates = value; - _isAutoCheckForUpdatesChangedEvent(value); + IsAutoCheckForUpdatesChanged.Invoke(value); SaveAsync(); } - event_token IsAutoCheckForUpdatesChanged(delegate const& handler) { - return _isAutoCheckForUpdatesChangedEvent.add(handler); - } - - WinRTUtils::EventRevoker IsAutoCheckForUpdatesChanged(auto_revoke_t, delegate const& handler) { - event_token token = IsAutoCheckForUpdatesChanged(handler); - return WinRTUtils::EventRevoker([this, token]() { - IsAutoCheckForUpdatesChanged(token); - }); - } - - void IsAutoCheckForUpdatesChanged(event_token const& token) { - _isAutoCheckForUpdatesChangedEvent.remove(token); - } - bool IsCheckForPreviewUpdates() const noexcept { return _isCheckForPreviewUpdates; } @@ -373,6 +283,13 @@ class AppSettings : private _AppSettingsData { SaveAsync(); } + WinRTUtils::Event> ThemeChanged; + WinRTUtils::Event> ShortcutChanged; + WinRTUtils::Event> IsAutoRestoreChanged; + WinRTUtils::Event> CountdownSecondsChanged; + WinRTUtils::Event> IsShowNotifyIconChanged; + WinRTUtils::Event> IsAutoCheckForUpdatesChanged; + private: AppSettings() = default; @@ -391,17 +308,10 @@ class AppSettings : private _AppSettingsData { bool _SetDefaultShortcuts() noexcept; void _SetDefaultScalingModes() noexcept; - void _UpdateConfigPath(std::wstring* existingConfigPath = nullptr) noexcept; + bool _UpdateConfigPath(std::wstring* existingConfigPath = nullptr) noexcept; // 用于同步保存 - Win32Utils::SRWMutex _saveMutex; - - event> _themeChangedEvent; - event> _shortcutChangedEvent; - event> _isAutoRestoreChangedEvent; - event> _countdownSecondsChangedEvent; - event> _isShowNotifyIconChangedEvent; - event> _isAutoCheckForUpdatesChangedEvent; + wil::srwlock _saveLock; }; } diff --git a/src/Magpie.App/AppXReader.cpp b/src/Magpie.App/AppXReader.cpp index 0345126a2..f241f2267 100644 --- a/src/Magpie.App/AppXReader.cpp +++ b/src/Magpie.App/AppXReader.cpp @@ -29,7 +29,7 @@ struct AppxCacheData { }; static phmap::flat_hash_map appxCache; // 用于同步对 appxCache 的访问 -static Win32Utils::SRWMutex appxCacheMutex; +static wil::srwlock appxCacheLock; static std::wstring ResourceFromPri(std::wstring_view packageFullName, std::wstring_view resourceReference) { @@ -133,15 +133,13 @@ bool AppXReader::Initialize(HWND hWnd) noexcept { return false; } - PROPVARIANT prop; + wil::unique_prop_variant prop; hr = propStore->GetValue(PKEY_AppUserModel_ID, &prop); if (FAILED(hr) || prop.vt != VT_LPWSTR || !prop.pwszVal) { return false; } - bool result = Initialize(prop.pwszVal); - PropVariantClear(&prop); - return result; + return Initialize(prop.pwszVal); } // 使用 GetApplicationUserModelId 获取 AUMID @@ -151,19 +149,26 @@ bool AppXReader::Initialize(HWND hWnd) noexcept { return false; } - Win32Utils::ScopedHandle hProc(Win32Utils::SafeHandle(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwProcId))); + wil::unique_process_handle hProc(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwProcId)); if (!hProc) { Logger::Get().Win32Error("OpenProcess 失败"); return false; } - UINT32 length = 0; - if (GetApplicationUserModelId(hProc.get(), &length, nullptr) == APPMODEL_ERROR_NO_APPLICATION || length == 0) { - return false; - } + std::wstring aumid; + HRESULT hr = wil::AdaptFixedSizeToAllocatedResult( + aumid, [&](_Out_writes_(valueLength) PWSTR value, size_t valueLength, _Out_ size_t* valueLengthNeededWithNul) -> HRESULT { + UINT32 length = (UINT32)valueLength; + LONG rc = GetApplicationUserModelId(hProc.get(), &length, value); + if (rc != ERROR_SUCCESS && rc != ERROR_INSUFFICIENT_BUFFER) { + return HRESULT_FROM_WIN32(rc); + } - std::wstring aumid((size_t)length - 1, 0); - if (GetApplicationUserModelId(hProc.get(), &length, aumid.data()) != ERROR_SUCCESS) { + *valueLengthNeededWithNul = length; + return S_OK; + } + ); + if (FAILED(hr)) { return false; } @@ -172,7 +177,7 @@ bool AppXReader::Initialize(HWND hWnd) noexcept { bool AppXReader::Initialize(std::wstring_view aumid) noexcept { { - std::scoped_lock lk(appxCacheMutex); + auto lock = appxCacheLock.lock_exclusive(); auto it = appxCache.find(aumid); if (it != appxCache.end()) { @@ -197,6 +202,7 @@ bool AppXReader::Initialize(std::wstring_view aumid) noexcept { _aumid = aumid; if (!_ResolvePackagePath()) { + Logger::Get().Error("_ResolvePackagePath 失败"); return false; } @@ -247,45 +253,36 @@ bool AppXReader::Initialize(std::wstring_view aumid) noexcept { break; } - wchar_t* curPraid = nullptr; - if (FAILED(appxApp->GetStringValue(L"Id", &curPraid))) { + wil::unique_cotaskmem_string curPraid; + if (FAILED(appxApp->GetStringValue(L"Id", curPraid.put()))) { break; } - if (curPraid) { - if (_praid == curPraid) { - CoTaskMemFree(curPraid); - - wchar_t* value = nullptr; - if (SUCCEEDED(appxApp->GetStringValue(L"DisplayName", &value)) && value) { - _displayName = value; - CoTaskMemFree(value); - } - - value = nullptr; - if (SUCCEEDED(appxApp->GetStringValue(L"Executable", &value)) && value) { - _executable = value; - CoTaskMemFree(value); - } + if (curPraid && _praid == curPraid.get()) { + wil::unique_cotaskmem_string value; + if (SUCCEEDED(appxApp->GetStringValue(L"DisplayName", value.put())) && value) { + _displayName = value.get(); + } - value = nullptr; - if (SUCCEEDED(appxApp->GetStringValue(L"Square44x44Logo", &value)) && value) { - _square44x44Logo = value; - CoTaskMemFree(value); - } + value = nullptr; + if (SUCCEEDED(appxApp->GetStringValue(L"Executable", value.put())) && value) { + _executable = value.get(); + } - std::scoped_lock lk(appxCacheMutex); - AppxCacheData& cacheData = appxCache[aumid]; - cacheData.praid = _praid; - cacheData.packageFullName = _packageFullName; - cacheData.packagePath = _packagePath; - cacheData.displayName = _displayName; - cacheData.executable = _executable; - cacheData.square44x44Logo = _square44x44Logo; - return true; + value = nullptr; + if (SUCCEEDED(appxApp->GetStringValue(L"Square44x44Logo", value.put())) && value) { + _square44x44Logo = value.get(); } - CoTaskMemFree(curPraid); + auto lock = appxCacheLock.lock_exclusive(); + AppxCacheData& cacheData = appxCache[aumid]; + cacheData.praid = _praid; + cacheData.packageFullName = _packageFullName; + cacheData.packagePath = _packagePath; + cacheData.displayName = _displayName; + cacheData.executable = _executable; + cacheData.square44x44Logo = _square44x44Logo; + return true; } hr = appEnumerator->MoveNext(&hasCurrent); @@ -317,7 +314,7 @@ std::wstring AppXReader::GetExecutablePath() noexcept { return {}; } - return StrUtils::Concat(_packagePath, _executable); + return _packagePath + _executable; } class CandidateIcon { @@ -425,8 +422,7 @@ class CandidateIcon { return _isValid; } - // 用以选择更合适的图标 - // 规则: + // 用以选择更合适的图标。规则: // 1. 匹配当前主题的优先 // 2. 没有边框的优先 // 3. 和 preferredSize 相同的优先 @@ -545,8 +541,8 @@ static SoftwareBitmap AutoFillBackground(const std::wstring& iconPath, bool isLi // 计算平均亮度 float lumaTotal = 0; - UINT lumaCount = 0; - for (UINT i = 0, len = width * height; i < len; ++i) { + uint32_t lumaCount = 0; + for (uint32_t i = 0, len = width * height; i < len; ++i) { uint8_t* pixel = &buf.get()[i * 4]; uint8_t alpha = pixel[3]; @@ -564,7 +560,7 @@ static SoftwareBitmap AutoFillBackground(const std::wstring& iconPath, bool isLi ++lumaCount; } - float lumaAvg = lumaTotal / lumaCount; + const float lumaAvg = lumaTotal / lumaCount; if (isLightTheme ? lumaAvg <= 220 : lumaAvg >= 30) { if (!noPath) { return nullptr; @@ -578,7 +574,7 @@ static SoftwareBitmap AutoFillBackground(const std::wstring& iconPath, bool isLi const uint8_t* origin = buf.get(); for (size_t i = 0, pixelsSize = static_cast(width) * height * 4; i < pixelsSize; i += 4) { // 预乘 Alpha 通道 - float alpha = origin[i + 3] / 255.0f; + const float alpha = origin[i + 3] / 255.0f; pixels[i] = (BYTE)std::lround(origin[i] * alpha); pixels[i + 1] = (BYTE)std::lround(origin[i + 1] * alpha); @@ -590,10 +586,10 @@ static SoftwareBitmap AutoFillBackground(const std::wstring& iconPath, bool isLi } // 和背景的对比度太低,需要填充背景 - const UINT borderWidth = width / 6; - const UINT borderHeight = height / 6; - const UINT totalWidth = width + borderWidth * 2; - const UINT totalHeight = height + borderHeight * 2; + const uint32_t borderWidth = width / 6; + const uint32_t borderHeight = height / 6; + const uint32_t totalWidth = width + borderWidth * 2; + const uint32_t totalHeight = height + borderHeight * 2; const Color accentColor = UISettings().GetColorValue(UIColorType::Accent); @@ -673,7 +669,7 @@ std::variant AppXReader::GetIcon( std::vector candidateIcons; WIN32_FIND_DATA findData{}; - HANDLE hFind = Win32Utils::SafeHandle(FindFirstFileEx(StrUtils::Concat(prefix, L"*").c_str(), + wil::unique_hfind hFind(FindFirstFileEx(StrUtils::Concat(prefix, L"*").c_str(), FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr, FIND_FIRST_EX_LARGE_FETCH)); if (hFind) { do { @@ -687,9 +683,7 @@ std::variant AppXReader::GetIcon( } candidateIcons.emplace_back(std::move(ci)); - } while (FindNextFile(hFind, &findData)); - - FindClose(hFind); + } while (FindNextFile(hFind.get(), &findData)); } if (candidateIcons.empty()) { @@ -717,7 +711,7 @@ std::variant AppXReader::GetIcon( } void AppXReader::ClearCache() noexcept { - std::scoped_lock lk(appxCacheMutex); + auto lock = appxCacheLock.lock_exclusive(); appxCache.clear(); } @@ -764,19 +758,23 @@ bool AppXReader::_ResolvePackagePath() { // 只使用第一个包,一般也只有一个 _packageFullName = packageFullNames[0]; - uint32_t pathLen = 0; - GetPackagePathByFullName(_packageFullName.c_str(), &pathLen, nullptr); - if (pathLen == 0) { - Logger::Get().Error("GetPackagePathByFullName 失败"); - return false; - } + HRESULT hr = wil::AdaptFixedSizeToAllocatedResult( + _packagePath, [&](_Out_writes_(valueLength) PWSTR value, size_t valueLength, _Out_ size_t* valueLengthNeededWithNul) -> HRESULT { + UINT32 length = (UINT32)valueLength; + LONG rc = GetPackagePathByFullName(_packageFullName.c_str(), &length, value); + if (rc != ERROR_SUCCESS && rc != ERROR_INSUFFICIENT_BUFFER) { + return HRESULT_FROM_WIN32(rc); + } - _packagePath.resize((size_t)pathLen - 1); - if (GetPackagePathByFullName(_packageFullName.c_str(), &pathLen, _packagePath.data()) != ERROR_SUCCESS) { - Logger::Get().Error("GetPackagePathByFullName 失败"); - _packagePath.clear(); + *valueLengthNeededWithNul = length; + return S_OK; + } + ); + if (FAILED(hr)) { + Logger::Get().ComError("GetPackagePathByFullName 失败", hr); return false; } + if (_packagePath.back() != L'\\') { _packagePath.push_back(L'\\'); } diff --git a/src/Magpie.App/AutoStartHelper.cpp b/src/Magpie.App/AutoStartHelper.cpp index 117df2d50..7bd74517d 100644 --- a/src/Magpie.App/AutoStartHelper.cpp +++ b/src/Magpie.App/AutoStartHelper.cpp @@ -25,15 +25,15 @@ namespace winrt::Magpie::App { -static constexpr const DWORD USERNAME_DOMAIN_LEN = DNLEN + UNLEN + 2; // Domain Name + '\' + User Name + '\0' -static constexpr const DWORD USERNAME_LEN = UNLEN + 1; // User Name + '\0' +static constexpr DWORD USERNAME_DOMAIN_LEN = DNLEN + UNLEN + 2; // Domain Name + '\' + User Name + '\0' +static constexpr DWORD USERNAME_LEN = UNLEN + 1; // User Name + '\0' -static std::wstring GetTaskName(std::wstring_view userName) { +static std::wstring GetTaskName(std::wstring_view userName) noexcept { return StrUtils::Concat(L"Autorun for ", userName); } -static com_ptr CreateTaskService() { +static com_ptr CreateTaskService() noexcept { com_ptr taskService = try_create_instance(CLSID_TaskScheduler); if (!taskService) { Logger::Get().Error("创建 TaskService 失败"); @@ -50,7 +50,7 @@ static com_ptr CreateTaskService() { return taskService; } -static bool CreateAutoStartTask(bool runElevated, const wchar_t* arguments) { +static bool CreateAutoStartTask(bool runElevated, const wchar_t* arguments) noexcept { WCHAR usernameDomain[USERNAME_DOMAIN_LEN]; WCHAR username[USERNAME_LEN]; @@ -74,16 +74,16 @@ static bool CreateAutoStartTask(bool runElevated, const wchar_t* arguments) { // 获取/创建 Magpie 文件夹 com_ptr taskFolder; - HRESULT hr = taskService->GetFolder(Win32Utils::BStr(L"\\Magpie"), taskFolder.put()); + HRESULT hr = taskService->GetFolder(wil::make_bstr_nothrow(L"\\Magpie").get(), taskFolder.put()); if (FAILED(hr)) { com_ptr rootFolder = NULL; - hr = taskService->GetFolder(Win32Utils::BStr(L"\\"), rootFolder.put()); + hr = taskService->GetFolder(wil::make_bstr_nothrow(L"\\").get(), rootFolder.put()); if (FAILED(hr)) { Logger::Get().ComError("获取根目录失败", hr); return false; } - hr = rootFolder->CreateFolder(Win32Utils::BStr(L"\\Magpie"), Win32Utils::Variant(L""), taskFolder.put()); + hr = rootFolder->CreateFolder(wil::make_bstr_nothrow(L"\\Magpie").get(), Win32Utils::Variant(L""), taskFolder.put()); if (FAILED(hr)) { Logger::Get().ComError("创建 Magpie 任务文件夹失败", hr); return false; @@ -107,7 +107,7 @@ static bool CreateAutoStartTask(bool runElevated, const wchar_t* arguments) { return false; } - hr = regInfo->put_Author(Win32Utils::BStr(usernameDomain)); + hr = regInfo->put_Author(wil::make_bstr_nothrow(usernameDomain).get()); if (FAILED(hr)) { Logger::Get().ComError("IRegistrationInfo::put_Author 失败", hr); return false; @@ -132,7 +132,7 @@ static bool CreateAutoStartTask(bool runElevated, const wchar_t* arguments) { Logger::Get().ComError("ITaskSettings::put_StopIfGoingOnBatteries 失败", hr); return false; } - hr = taskSettings->put_ExecutionTimeLimit(Win32Utils::BStr(L"PT0S")); //Unlimited + hr = taskSettings->put_ExecutionTimeLimit(wil::make_bstr_nothrow(L"PT0S").get()); //Unlimited if (FAILED(hr)) { Logger::Get().ComError("ITaskSettings::put_ExecutionTimeLimit 失败", hr); return false; @@ -167,15 +167,15 @@ static bool CreateAutoStartTask(bool runElevated, const wchar_t* arguments) { return false; } - logonTrigger->put_Id(Win32Utils::BStr(L"Trigger1")); + logonTrigger->put_Id(wil::make_bstr_nothrow(L"Trigger1").get()); // Timing issues may make explorer not be started when the task runs. // Add a little delay to mitigate this. - logonTrigger->put_Delay(Win32Utils::BStr(L"PT03S")); + logonTrigger->put_Delay(wil::make_bstr_nothrow(L"PT03S").get()); // Define the user. The task will execute when the user logs on. // The specified user must be a user on this computer. - hr = logonTrigger->put_UserId(Win32Utils::BStr(usernameDomain)); + hr = logonTrigger->put_UserId(wil::make_bstr_nothrow(usernameDomain).get()); if (FAILED(hr)) { Logger::Get().ComError("ILogonTrigger::put_UserId 失败", hr); return false; @@ -210,16 +210,15 @@ static bool CreateAutoStartTask(bool runElevated, const wchar_t* arguments) { } // Set the path of the executable to Magpie (passed as CustomActionData). - WCHAR executablePath[MAX_PATH]; - GetModuleFileName(NULL, executablePath, MAX_PATH); - hr = execAction->put_Path(Win32Utils::BStr(executablePath)); + const std::wstring& exePath = Win32Utils::GetExePath(); + hr = execAction->put_Path(wil::make_bstr_nothrow(exePath.c_str()).get()); if (FAILED(hr)) { Logger::Get().ComError("设置可执行文件路径失败", hr); return false; } if (arguments) { - execAction->put_Arguments(Win32Utils::BStr(arguments)); + execAction->put_Arguments(wil::make_bstr_nothrow(arguments).get()); } } @@ -234,8 +233,8 @@ static bool CreateAutoStartTask(bool runElevated, const wchar_t* arguments) { } // Set up principal information: - principal->put_Id(Win32Utils::BStr(L"Principal1")); - principal->put_UserId(Win32Utils::BStr(usernameDomain)); + principal->put_Id(wil::make_bstr_nothrow(L"Principal1").get()); + principal->put_UserId(wil::make_bstr_nothrow(usernameDomain).get()); principal->put_LogonType(TASK_LOGON_INTERACTIVE_TOKEN); if (runElevated) { @@ -259,7 +258,7 @@ static bool CreateAutoStartTask(bool runElevated, const wchar_t* arguments) { // 如果用户是 Administrator 账户,但 Magpie 不是以提升权限运行的,此调用会因权限问题失败 std::wstring taskName = GetTaskName(username); hr = taskFolder->RegisterTaskDefinition( - Win32Utils::BStr(taskName), + wil::make_bstr_nothrow(taskName.c_str()).get(), task.get(), TASK_CREATE_OR_UPDATE, Win32Utils::Variant(usernameDomain), @@ -279,7 +278,7 @@ static bool CreateAutoStartTask(bool runElevated, const wchar_t* arguments) { return true; } -static bool DeleteAutoStartTask() { +static bool DeleteAutoStartTask() noexcept { WCHAR username[USERNAME_LEN]; if (!GetEnvironmentVariable(L"USERNAME", username, USERNAME_LEN)) { Logger::Get().Win32Error("获取用户名失败"); @@ -292,23 +291,23 @@ static bool DeleteAutoStartTask() { } com_ptr taskFolder; - HRESULT hr = taskService->GetFolder(Win32Utils::BStr(L"\\Magpie"), taskFolder.put()); + HRESULT hr = taskService->GetFolder(wil::make_bstr_nothrow(L"\\Magpie").get(), taskFolder.put()); if (FAILED(hr)) { return true; } - Win32Utils::BStr taskName(GetTaskName(username)); + wil::unique_bstr taskName = wil::make_bstr_nothrow(GetTaskName(username).c_str()); { com_ptr existingRegisteredTask; - hr = taskFolder->GetTask(taskName, existingRegisteredTask.put()); + hr = taskFolder->GetTask(taskName.get(), existingRegisteredTask.put()); if (FAILED(hr)) { // 不存在任务 return true; } } - hr = taskFolder->DeleteTask(taskName, 0); + hr = taskFolder->DeleteTask(taskName.get(), 0); if (FAILED(hr)) { Logger::Get().ComError("删除任务失败", hr); return false; @@ -317,7 +316,7 @@ static bool DeleteAutoStartTask() { return true; } -static bool IsAutoStartTaskActive(std::wstring& arguements) { +static bool IsAutoStartTaskActive(std::wstring& arguements) noexcept { WCHAR username[USERNAME_LEN]; if (!GetEnvironmentVariable(L"USERNAME", username, USERNAME_LEN)) { Logger::Get().Win32Error("获取用户名失败"); @@ -330,13 +329,13 @@ static bool IsAutoStartTaskActive(std::wstring& arguements) { } com_ptr taskFolder; - HRESULT hr = taskService->GetFolder(Win32Utils::BStr(L"\\Magpie"), taskFolder.put()); + HRESULT hr = taskService->GetFolder(wil::make_bstr_nothrow(L"\\Magpie").get(), taskFolder.put()); if (FAILED(hr)) { return false; } com_ptr existingRegisteredTask; - hr = taskFolder->GetTask(Win32Utils::BStr(GetTaskName(username)), existingRegisteredTask.put()); + hr = taskFolder->GetTask(wil::make_bstr_nothrow(GetTaskName(username).c_str()).get(), existingRegisteredTask.put()); if (FAILED(hr)) { return false; } @@ -371,45 +370,37 @@ static bool IsAutoStartTaskActive(std::wstring& arguements) { } com_ptr execAction = action.try_as(); - Win32Utils::BStr args; - hr = execAction->get_Arguments(&args.Raw()); + wil::unique_bstr args; + hr = execAction->get_Arguments(args.put()); if (FAILED(hr)) { Logger::Get().ComError("获取参数失败", hr); return false; } - arguements = args.ToString(); + arguements = args ? args.get() : L""; return isEnabled == VARIANT_TRUE; } -static std::wstring GetShortcutPath() { - std::wstring shortcutPath; - +static std::wstring GetShortcutPath() noexcept { // 获取用户的启动文件夹路径 - wchar_t* startupDir = nullptr; - HRESULT hr = SHGetKnownFolderPath(FOLDERID_Startup, 0, NULL, &startupDir); + wil::unique_cotaskmem_string startupDir; + HRESULT hr = SHGetKnownFolderPath(FOLDERID_Startup, 0, NULL, startupDir.put()); if (FAILED(hr)) { - CoTaskMemFree(startupDir); Logger::Get().ComError("获取启动文件夹失败", hr); return {}; } - shortcutPath = StrUtils::Concat(startupDir, L"\\Magpie.lnk"); - CoTaskMemFree(startupDir); - - return shortcutPath; + return StrUtils::Concat(startupDir.get(), L"\\Magpie.lnk"); } -static bool CreateAutoStartShortcut(const wchar_t* arguments) { +static bool CreateAutoStartShortcut(const wchar_t* arguments) noexcept { com_ptr shellLink = try_create_instance(CLSID_ShellLink); if (!shellLink) { Logger::Get().Error("创建 ShellLink 失败"); return false; } - WCHAR executablePath[MAX_PATH]; - GetModuleFileName(NULL, executablePath, MAX_PATH); - shellLink->SetPath(executablePath); + shellLink->SetPath(Win32Utils::GetExePath().c_str()); if (arguments) { shellLink->SetArguments(arguments); @@ -430,17 +421,13 @@ static bool CreateAutoStartShortcut(const wchar_t* arguments) { return true; } -static bool DeleteAutoStartShortcut() { +static bool DeleteAutoStartShortcut() noexcept { std::wstring shortcutPath = GetShortcutPath(); if (shortcutPath.empty()) { return false; } - if (!Win32Utils::FileExists(shortcutPath.c_str())) { - return true; - } - - if (!DeleteFile(shortcutPath.c_str())) { + if (!DeleteFile(shortcutPath.c_str()) && GetLastError() != ERROR_FILE_NOT_FOUND) { Logger::Get().Win32Error("删除快捷方式失败"); return false; } @@ -448,7 +435,7 @@ static bool DeleteAutoStartShortcut() { return true; } -static bool IsAutoStartShortcutExist(std::wstring& arguments) { +static bool IsAutoStartShortcutExist(std::wstring& arguments) noexcept { std::wstring shortcutPath = GetShortcutPath(); if (shortcutPath.empty()) { return false; @@ -490,7 +477,7 @@ static bool IsAutoStartShortcutExist(std::wstring& arguments) { return false; } - PROPVARIANT prop; + wil::unique_prop_variant prop; hr = propertyStore->GetValue(PKEY_Link_Arguments, &prop); if (FAILED(hr)) { Logger::Get().ComError("检索 Arguments 参数失败", hr); @@ -503,12 +490,10 @@ static bool IsAutoStartShortcutExist(std::wstring& arguments) { arguments = prop.bstrVal; } - PropVariantClear(&prop); - return true; } -bool AutoStartHelper::EnableAutoStart(bool runElevated, const wchar_t* arguments) { +bool AutoStartHelper::EnableAutoStart(bool runElevated, const wchar_t* arguments) noexcept { if (CreateAutoStartTask(runElevated, arguments)) { DeleteAutoStartShortcut(); return true; @@ -517,13 +502,14 @@ bool AutoStartHelper::EnableAutoStart(bool runElevated, const wchar_t* arguments return CreateAutoStartShortcut(arguments); } -bool AutoStartHelper::DisableAutoStart() { +bool AutoStartHelper::DisableAutoStart() noexcept { + // 避免或运算符的短路,确保两者都被删除 bool result1 = DeleteAutoStartTask(); bool result2 = DeleteAutoStartShortcut(); return result1 || result2; } -bool AutoStartHelper::IsAutoStartEnabled(std::wstring& arguments) { +bool AutoStartHelper::IsAutoStartEnabled(std::wstring& arguments) noexcept { return IsAutoStartTaskActive(arguments) || IsAutoStartShortcutExist(arguments); } diff --git a/src/Magpie.App/AutoStartHelper.h b/src/Magpie.App/AutoStartHelper.h index 8b23ef98f..b58a3297c 100644 --- a/src/Magpie.App/AutoStartHelper.h +++ b/src/Magpie.App/AutoStartHelper.h @@ -3,9 +3,9 @@ namespace winrt::Magpie::App { struct AutoStartHelper { - static bool EnableAutoStart(bool runElevated, const wchar_t* arguments); - static bool DisableAutoStart(); - static bool IsAutoStartEnabled(std::wstring& arguments); + static bool EnableAutoStart(bool runElevated, const wchar_t* arguments) noexcept; + static bool DisableAutoStart() noexcept; + static bool IsAutoStartEnabled(std::wstring& arguments) noexcept; }; } diff --git a/src/Magpie.App/BlueInfoBar.xaml b/src/Magpie.App/BlueInfoBar.xaml new file mode 100644 index 000000000..542387170 --- /dev/null +++ b/src/Magpie.App/BlueInfoBar.xaml @@ -0,0 +1,18 @@ + + + + + + + #FF5fb2f2 + + + + + #FF0063b1 + + + diff --git a/src/Magpie.App/CandidateWindowItem.cpp b/src/Magpie.App/CandidateWindowItem.cpp index 82a72c383..40666d55e 100644 --- a/src/Magpie.App/CandidateWindowItem.cpp +++ b/src/Magpie.App/CandidateWindowItem.cpp @@ -8,7 +8,6 @@ #include "IconHelper.h" #include "StrUtils.h" - using namespace winrt; using namespace Windows::UI::ViewManagement; using namespace Windows::UI::Xaml::Controls; @@ -16,7 +15,6 @@ using namespace Windows::UI::Xaml::Media::Imaging; using namespace Windows::Graphics::Imaging; using namespace Windows::Graphics::Display; - namespace winrt::Magpie::App::implementation { static std::wstring GetProcessDesc(HWND hWnd) { @@ -37,7 +35,7 @@ static std::wstring GetProcessDesc(HWND hWnd) { return {}; } - std::unique_ptr infoData(new uint8_t[infoSize]); + std::unique_ptr infoData = std::make_unique(infoSize); if (!GetFileVersionInfoEx(FILE_VER_GET_LOCALISED, fileName.c_str(), 0, infoSize, infoData.get())) { return {}; } @@ -121,7 +119,7 @@ fire_and_forget CandidateWindowItem::_ResolveWindow(bool resolveIcon, bool resol } // 即使 defaultProfileName 为空也通知 DefaultProfileName 已更改 // 这是为了正确设置 CandidateWindowIndex - that->_propertyChangedEvent(*that, PropertyChangedEventArgs(L"DefaultProfileName")); + that->RaisePropertyChanged(L"DefaultProfileName"); that->_aumid = aumid; } @@ -167,7 +165,7 @@ fire_and_forget CandidateWindowItem::_ResolveWindow(bool resolveIcon, bool resol strongThis->_icon = std::move(fontIcon); } - strongThis->_propertyChangedEvent(*this, PropertyChangedEventArgs(L"Icon")); + strongThis->RaisePropertyChanged(L"Icon"); } } diff --git a/src/Magpie.App/CandidateWindowItem.h b/src/Magpie.App/CandidateWindowItem.h index d0b51eb6a..e873d3645 100644 --- a/src/Magpie.App/CandidateWindowItem.h +++ b/src/Magpie.App/CandidateWindowItem.h @@ -3,17 +3,10 @@ namespace winrt::Magpie::App::implementation { -struct CandidateWindowItem : CandidateWindowItemT { +struct CandidateWindowItem : CandidateWindowItemT, + wil::notify_property_changed_base { CandidateWindowItem(uint64_t hWnd, uint32_t dpi, bool isLightTheme, CoreDispatcher const& dispatcher); - event_token PropertyChanged(PropertyChangedEventHandler const& handler) { - return _propertyChangedEvent.add(handler); - } - - void PropertyChanged(event_token const& token) noexcept { - _propertyChangedEvent.remove(token); - } - hstring Title() const noexcept { return _title; } @@ -39,8 +32,6 @@ struct CandidateWindowItem : CandidateWindowItemT { private: fire_and_forget _ResolveWindow(bool resolveIcon, bool resolveName, HWND hWnd, bool isLightTheme, uint32_t dpi, CoreDispatcher dispatcher); - event _propertyChangedEvent; - hstring _title; Controls::IconElement _icon{ nullptr }; hstring _defaultProfileName; diff --git a/src/Magpie.App/EffectParametersViewModel.h b/src/Magpie.App/EffectParametersViewModel.h index 7f69b9d91..b4cbd7fe0 100644 --- a/src/Magpie.App/EffectParametersViewModel.h +++ b/src/Magpie.App/EffectParametersViewModel.h @@ -10,19 +10,11 @@ struct EffectInfo; namespace winrt::Magpie::App::implementation { -struct ScalingModeBoolParameter : ScalingModeBoolParameterT { +struct ScalingModeBoolParameter : ScalingModeBoolParameterT, + wil::notify_property_changed_base { ScalingModeBoolParameter(uint32_t index, const hstring& label, bool initValue) : _index(index), _label(box_value(label)), _value(initValue) { } - - event_token PropertyChanged(PropertyChangedEventHandler const& handler) { - return _propertyChangedEvent.add(handler); - } - - void PropertyChanged(event_token const& token) noexcept { - _propertyChangedEvent.remove(token); - } - uint32_t Index() const noexcept { return _index; } @@ -33,7 +25,7 @@ struct ScalingModeBoolParameter : ScalingModeBoolParameterT _propertyChangedEvent; - const uint32_t _index; IInspectable _label; bool _value; }; -struct ScalingModeFloatParameter : ScalingModeFloatParameterT { +struct ScalingModeFloatParameter : ScalingModeFloatParameterT, + wil::notify_property_changed_base { ScalingModeFloatParameter(uint32_t index, const hstring& label, float initValue, float minimum, float maximum, float step) : _index(index), _label(label), _value(initValue), _minimum(minimum), _maximum(maximum), _step(step) { } - event_token PropertyChanged(PropertyChangedEventHandler const& handler) { - return _propertyChangedEvent.add(handler); - } - - void PropertyChanged(event_token const& token) noexcept { - _propertyChangedEvent.remove(token); - } - uint32_t Index() const noexcept { return _index; } @@ -71,12 +54,12 @@ struct ScalingModeFloatParameter : ScalingModeFloatParameterT _propertyChangedEvent; - const uint32_t _index; const hstring _label; const double _minimum; diff --git a/src/Magpie.App/EffectsService.cpp b/src/Magpie.App/EffectsService.cpp index 92da838df..1322268d3 100644 --- a/src/Magpie.App/EffectsService.cpp +++ b/src/Magpie.App/EffectsService.cpp @@ -20,7 +20,7 @@ static void ListEffects(std::vector& result, std::wstring_view pre result.reserve(80); WIN32_FIND_DATA findData{}; - HANDLE hFind = Win32Utils::SafeHandle(FindFirstFileEx( + wil::unique_hfind hFind(FindFirstFileEx( StrUtils::Concat(CommonSharedConstants::EFFECTS_DIR, prefix, L"*").c_str(), FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr, FIND_FIRST_EX_LARGE_FETCH)); if (hFind) { @@ -40,9 +40,7 @@ static void ListEffects(std::vector& result, std::wstring_view pre } result.emplace_back(StrUtils::Concat(prefix, fileName.substr(0, fileName.size() - 5))); - } while (FindNextFile(hFind, &findData)); - - FindClose(hFind); + } while (FindNextFile(hFind.get(), &findData)); } else { Logger::Get().Win32Error("查找缓存文件失败"); } @@ -55,26 +53,23 @@ fire_and_forget EffectsService::StartInitialize() { ListEffects(effectNames); const uint32_t nEffect = (uint32_t)effectNames.size(); + _effectsMap.reserve(nEffect); + _effects.reserve(nEffect); + + // 用于同步 _effectsMap 和 _effects 的初始化 + wil::srwlock srwLock; - std::vector descs(nEffect); + // 并行解析效果 Win32Utils::RunParallel([&](uint32_t id) { - descs[id].name = StrUtils::UTF16ToUTF8(effectNames[id]); - if (EffectCompiler::Compile(descs[id], EffectCompilerFlags::NoCompile)) { - descs[id].name.clear(); - } - }, nEffect); + EffectDesc effectDesc; - _effectsMap.reserve(nEffect); - - for (uint32_t i = 0; i < nEffect; ++i) { - EffectDesc& effectDesc = descs[i]; - if (effectDesc.name.empty()) { - continue; + effectDesc.name = StrUtils::UTF16ToUTF8(effectNames[id]); + if (EffectCompiler::Compile(effectDesc, EffectCompilerFlags::NoCompile)) { + return; } - // 这里修改 _effects 无需同步,因为用户界面尚未显示 - EffectInfo& effect = _effects.emplace_back(); - effect.name = std::move(effectNames[i]); + EffectInfo effect; + effect.name = std::move(effectNames[id]); if (effectDesc.sortName.empty()) { effect.sortName = effect.name; @@ -89,22 +84,23 @@ fire_and_forget EffectsService::StartInitialize() { ); } } - + effect.params = std::move(effectDesc.params); if (effectDesc.GetOutputSizeExpr().first.empty()) { effect.flags |= EffectInfoFlags::CanScale; } - _effectsMap.emplace(effect.name, (uint32_t)_effects.size() - 1); - } + auto lock = srwLock.lock_exclusive(); + _effectsMap.emplace(effect.name, (uint32_t)_effects.size()); + _effects.emplace_back(std::move(effect)); + }, nEffect); _initialized.store(true, std::memory_order_release); + _initialized.notify_one(); } void EffectsService::WaitForInitialize() { - while (!_initialized.load(std::memory_order_acquire)) { - Sleep(0); - } + _initialized.wait(false, std::memory_order_acquire); } } diff --git a/src/Magpie.App/EffectsService.h b/src/Magpie.App/EffectsService.h index 720841063..c3ae5357f 100644 --- a/src/Magpie.App/EffectsService.h +++ b/src/Magpie.App/EffectsService.h @@ -8,7 +8,7 @@ struct EffectParameterDesc; namespace winrt::Magpie::App { struct EffectInfoFlags { - static constexpr const uint32_t CanScale = 1; + static constexpr uint32_t CanScale = 1; }; struct EffectInfo { diff --git a/src/Magpie.App/FileDialogHelper.cpp b/src/Magpie.App/FileDialogHelper.cpp index ce5962352..7021d95f3 100644 --- a/src/Magpie.App/FileDialogHelper.cpp +++ b/src/Magpie.App/FileDialogHelper.cpp @@ -23,16 +23,14 @@ std::optional FileDialogHelper::OpenFileDialog(IFileDialog* fileDi return std::nullopt; } - wchar_t* fileName = nullptr; - hr = file->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &fileName); + wil::unique_cotaskmem_string fileName; + hr = file->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, fileName.put()); if (FAILED(hr)) { Logger::Get().ComError("IShellItem::GetDisplayName 失败", hr); return std::nullopt; } - std::wstring result(fileName); - CoTaskMemFree(fileName); - return std::move(result); + return std::wstring(fileName.get()); } } diff --git a/src/Magpie.App/HomePage.cpp b/src/Magpie.App/HomePage.cpp index d8c1fd5f3..477533d35 100644 --- a/src/Magpie.App/HomePage.cpp +++ b/src/Magpie.App/HomePage.cpp @@ -4,6 +4,7 @@ #include "HomePage.g.cpp" #endif #include "XamlUtils.h" +#include "ComboBoxHelper.h" namespace winrt::Magpie::App::implementation { @@ -12,4 +13,8 @@ void HomePage::TimerSlider_Loaded(IInspectable const& sender, RoutedEventArgs co XamlUtils::UpdateThemeOfTooltips(sender.as(), ActualTheme()); } +void HomePage::ComboBox_DropDownOpened(IInspectable const& sender, IInspectable const&) const { + ComboBoxHelper::DropDownOpened(*this, sender); +} + } diff --git a/src/Magpie.App/HomePage.h b/src/Magpie.App/HomePage.h index 1de4f67e8..d47e118e5 100644 --- a/src/Magpie.App/HomePage.h +++ b/src/Magpie.App/HomePage.h @@ -10,6 +10,8 @@ struct HomePage : HomePageT { return _viewModel; } + void ComboBox_DropDownOpened(IInspectable const& sender, IInspectable const&) const; + private: Magpie::App::HomeViewModel _viewModel; }; diff --git a/src/Magpie.App/HomePage.xaml b/src/Magpie.App/HomePage.xaml index 42654cd1a..d276a50a3 100644 --- a/src/Magpie.App/HomePage.xaml +++ b/src/Magpie.App/HomePage.xaml @@ -59,7 +59,7 @@ - + @@ -86,7 +86,7 @@ - + - @@ -104,7 +104,7 @@ - @@ -118,16 +118,108 @@ - - - - - @@ -509,14 +522,15 @@ - + - - + + \ No newline at end of file diff --git a/src/Magpie.App/ScalingModesService.cpp b/src/Magpie.App/ScalingModesService.cpp index 41b5fdcf3..1cadf11bd 100644 --- a/src/Magpie.App/ScalingModesService.cpp +++ b/src/Magpie.App/ScalingModesService.cpp @@ -29,7 +29,7 @@ void ScalingModesService::AddScalingMode(std::wstring_view name, int copyFrom) { scalingModes.emplace_back(scalingModes[copyFrom]).name = name; } - _scalingModeAddedEvent(EffectAddedWay::Add); + ScalingModeAdded.Invoke(EffectAddedWay::Add); AppSettings::Get().SaveAsync(); } @@ -51,7 +51,7 @@ void ScalingModesService::RemoveScalingMode(uint32_t index) { UpdateProfileAfterRemove(profile, (int)index); } - _scalingModeRemovedEvent(index); + ScalingModeRemoved.Invoke(index); AppSettings::Get().SaveAsync(); } @@ -82,7 +82,7 @@ bool ScalingModesService::MoveScalingMode(uint32_t scalingModeIdx, bool isMoveUp UpdateProfileAfterMove(profile, (int)scalingModeIdx, targetIdx); } - _scalingModeMovedEvent(scalingModeIdx, isMoveUp); + ScalingModeMoved.Invoke(scalingModeIdx, isMoveUp); AppSettings::Get().SaveAsync(); return true; @@ -286,7 +286,7 @@ bool ScalingModesService::Import(const rapidjson::GenericObject const& handler) { - return _scalingModeAddedEvent.add(handler); - } - - WinRTUtils::EventRevoker ScalingModeAdded(auto_revoke_t, delegate const& handler) { - event_token token = ScalingModeAdded(handler); - return WinRTUtils::EventRevoker([this, token]() { - ScalingModeAdded(token); - }); - } - - void ScalingModeAdded(event_token const& token) { - _scalingModeAddedEvent.remove(token); - } - void RemoveScalingMode(uint32_t index); - event_token ScalingModeRemoved(delegate const& handler) { - return _scalingModeRemovedEvent.add(handler); - } - - WinRTUtils::EventRevoker ScalingModeRemoved(auto_revoke_t, delegate const& handler) { - event_token token = ScalingModeRemoved(handler); - return WinRTUtils::EventRevoker([this, token]() { - ScalingModeRemoved(token); - }); - } - - void ScalingModeRemoved(event_token const& token) { - _scalingModeRemovedEvent.remove(token); - } - bool MoveScalingMode(uint32_t scalingModeIdx, bool isMoveUp); - event_token ScalingModeMoved(delegate const& handler) { - return _scalingModeMovedEvent.add(handler); - } - - WinRTUtils::EventRevoker ScalingModeMoved(auto_revoke_t, delegate const& handler) { - event_token token = ScalingModeMoved(handler); - return WinRTUtils::EventRevoker([this, token]() { - ScalingModeMoved(token); - }); - } - - void ScalingModeMoved(event_token const& token) { - _scalingModeMovedEvent.remove(token); - } - // 不能使用 rapidjson::Writer 类型,因为 PrettyWriter 没有重写 Writer 中的方法 // 不合理的 API 设计 void Export(rapidjson::PrettyWriter& writer) const noexcept; @@ -85,12 +40,13 @@ class ScalingModesService { bool Import(const rapidjson::GenericObject& root, bool loadingSettings) noexcept; bool ImportLegacy(const rapidjson::Document& doc) noexcept; + + WinRTUtils::Event> ScalingModeAdded; + WinRTUtils::Event> ScalingModeRemoved; + WinRTUtils::Event> ScalingModeMoved; + private: ScalingModesService() = default; - - event> _scalingModeAddedEvent; - event> _scalingModeRemovedEvent; - event> _scalingModeMovedEvent; }; } diff --git a/src/Magpie.App/ScalingConfigurationViewModel.cpp b/src/Magpie.App/ScalingModesViewModel.cpp similarity index 68% rename from src/Magpie.App/ScalingConfigurationViewModel.cpp rename to src/Magpie.App/ScalingModesViewModel.cpp index eeb89cd66..041745631 100644 --- a/src/Magpie.App/ScalingConfigurationViewModel.cpp +++ b/src/Magpie.App/ScalingModesViewModel.cpp @@ -1,7 +1,7 @@ #include "pch.h" -#include "ScalingConfigurationViewModel.h" -#if __has_include("ScalingConfigurationViewModel.g.cpp") -#include "ScalingConfigurationViewModel.g.cpp" +#include "ScalingModesViewModel.h" +#if __has_include("ScalingModesViewModel.g.cpp") +#include "ScalingModesViewModel.g.cpp" #endif #include "EffectsService.h" #include "AppSettings.h" @@ -17,20 +17,15 @@ using namespace ::Magpie::Core; namespace winrt::Magpie::App::implementation { -ScalingConfigurationViewModel::ScalingConfigurationViewModel() { - _scalingModesListTransitions.Append(Animation::ContentThemeTransition()); - Animation::RepositionThemeTransition respositionAnime; - respositionAnime.IsStaggeringEnabled(false); - _scalingModesListTransitions.Append(std::move(respositionAnime)); - +ScalingModesViewModel::ScalingModesViewModel() { _AddScalingModes(); _scalingModeAddedRevoker = ScalingModesService::Get().ScalingModeAdded( - auto_revoke, { this, &ScalingConfigurationViewModel::_ScalingModesService_Added }); + auto_revoke, { this, &ScalingModesViewModel::_ScalingModesService_Added }); _scalingModeMovedRevoker = ScalingModesService::Get().ScalingModeMoved( - auto_revoke, { this, &ScalingConfigurationViewModel::_ScalingModesService_Moved }); + auto_revoke, { this, &ScalingModesViewModel::_ScalingModesService_Moved }); _scalingModeRemovedRevoker = ScalingModesService::Get().ScalingModeRemoved( - auto_revoke, { this, &ScalingConfigurationViewModel::_ScalingModesService_Removed }); + auto_revoke, { this, &ScalingModesViewModel::_ScalingModesService_Removed }); } static std::optional OpenFileDialogForJson(IFileDialog* fileDialog) noexcept { @@ -45,7 +40,7 @@ static std::optional OpenFileDialogForJson(IFileDialog* fileDialog return FileDialogHelper::OpenFileDialog(fileDialog, FOS_STRICTFILETYPES); } -void ScalingConfigurationViewModel::Export() const noexcept { +void ScalingModesViewModel::Export() const noexcept { com_ptr fileDialog = try_create_instance(CLSID_FileSaveDialog); if (!fileDialog) { Logger::Get().Error("创建 FileSaveDialog 失败"); @@ -101,7 +96,7 @@ static bool ImportImpl(bool legacy) noexcept { // 导入时放宽 json 格式限制 doc.ParseInsitu(json.data()); if (doc.HasParseError()) { - Logger::Get().Error(fmt::format("解析缩放模式失败\n\t错误码:{}", (int)doc.GetParseError())); + Logger::Get().Error(fmt::format("解析缩放模式失败\n\t错误码: {}", (int)doc.GetParseError())); return false; } @@ -116,45 +111,45 @@ static bool ImportImpl(bool legacy) noexcept { return ScalingModesService::Get().Import(((const rapidjson::Document&)doc).GetObj(), false); } -void ScalingConfigurationViewModel::_Import(bool legacy) { +void ScalingModesViewModel::_Import(bool legacy) { ShowErrorMessage(false); if (!ImportImpl(legacy)) { ShowErrorMessage(true); } } -void ScalingConfigurationViewModel::PrepareForAdd() { +void ScalingModesViewModel::PrepareForAdd() { std::vector copyFromList; ResourceLoader resourceLoader = ResourceLoader::GetForCurrentView(CommonSharedConstants::APP_RESOURCE_MAP_ID); copyFromList.push_back(box_value(resourceLoader.GetString( - L"ScalingConfiguration_ScalingModes_NewScalingModeFlyout_CopyFrom_None"))); + L"ScalingModes_NewScalingModeFlyout_CopyFrom_None"))); for (const auto& scalingMode : AppSettings::Get().ScalingModes()) { copyFromList.push_back(box_value(scalingMode.name)); } _newScalingModeCopyFromList = single_threaded_vector(std::move(copyFromList)); - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"NewScalingModeCopyFromList")); + RaisePropertyChanged(L"NewScalingModeCopyFromList"); _newScalingModeName.clear(); - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"NewScalingModeName")); + RaisePropertyChanged(L"NewScalingModeName"); _newScalingModeCopyFrom = 0; - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"NewScalingModeCopyFrom")); + RaisePropertyChanged(L"NewScalingModeCopyFrom"); } -void ScalingConfigurationViewModel::NewScalingModeName(const hstring& value) noexcept { +void ScalingModesViewModel::NewScalingModeName(const hstring& value) noexcept { _newScalingModeName = value; - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"NewScalingModeName")); - _propertyChangedEvent(*this, PropertyChangedEventArgs(L"IsAddButtonEnabled")); + RaisePropertyChanged(L"NewScalingModeName"); + RaisePropertyChanged(L"IsAddButtonEnabled"); } -void ScalingConfigurationViewModel::AddScalingMode() { +void ScalingModesViewModel::AddScalingMode() { ScalingModesService::Get().AddScalingMode(_newScalingModeName, _newScalingModeCopyFrom - 1); } -fire_and_forget ScalingConfigurationViewModel::_AddScalingModes(bool isInitialExpanded) { +fire_and_forget ScalingModesViewModel::_AddScalingModes(bool isInitialExpanded) { if (_addingScalingModes) { co_return; } @@ -203,37 +198,21 @@ fire_and_forget ScalingConfigurationViewModel::_AddScalingModes(bool isInitialEx } _addingScalingModes = false; - - if (!_scalingModesInitialized) { - _scalingModesInitialized = true; - - // 在所有缩放模式初始化完毕后再展示添加/删除动画 - if (dispatcher) { - auto weakThis = get_weak(); - co_await 10ms; - co_await dispatcher; - if (!weakThis.get()) { - co_return; - } - } - - _scalingModesListTransitions.Append(Animation::AddDeleteThemeTransition()); - } } -void ScalingConfigurationViewModel::_ScalingModesService_Added(EffectAddedWay way) { +void ScalingModesViewModel::_ScalingModesService_Added(EffectAddedWay way) { _AddScalingModes(way == EffectAddedWay::Add); } -void ScalingConfigurationViewModel::_ScalingModesService_Moved(uint32_t index, bool isMoveUp) { - uint32_t targetIndex = isMoveUp ? index - 1 : index + 1; +void ScalingModesViewModel::_ScalingModesService_Moved(uint32_t index, bool isMoveUp) { + const uint32_t targetIndex = isMoveUp ? index - 1 : index + 1; ScalingModeItem targetItem = _scalingModes.GetAt(targetIndex).as(); _scalingModes.RemoveAt(targetIndex); _scalingModes.InsertAt(index, targetItem); } -void ScalingConfigurationViewModel::_ScalingModesService_Removed(uint32_t index) { +void ScalingModesViewModel::_ScalingModesService_Removed(uint32_t index) { _scalingModes.RemoveAt(index); } diff --git a/src/Magpie.App/ScalingConfigurationViewModel.h b/src/Magpie.App/ScalingModesViewModel.h similarity index 64% rename from src/Magpie.App/ScalingConfigurationViewModel.h rename to src/Magpie.App/ScalingModesViewModel.h index e5535049c..a8a4fbd58 100644 --- a/src/Magpie.App/ScalingConfigurationViewModel.h +++ b/src/Magpie.App/ScalingModesViewModel.h @@ -1,20 +1,13 @@ #pragma once -#include "ScalingConfigurationViewModel.g.h" +#include "ScalingModesViewModel.g.h" #include "WinRTUtils.h" #include "ScalingModesService.h" namespace winrt::Magpie::App::implementation { -struct ScalingConfigurationViewModel : ScalingConfigurationViewModelT { - ScalingConfigurationViewModel(); - - event_token PropertyChanged(PropertyChangedEventHandler const& handler) { - return _propertyChangedEvent.add(handler); - } - - void PropertyChanged(event_token const& token) noexcept { - _propertyChangedEvent.remove(token); - } +struct ScalingModesViewModel : ScalingModesViewModelT, + wil::notify_property_changed_base { + ScalingModesViewModel(); void Export() const noexcept; @@ -32,11 +25,7 @@ struct ScalingConfigurationViewModel : ScalingConfigurationViewModelT ScalingModes() const noexcept { @@ -61,7 +50,7 @@ struct ScalingConfigurationViewModel : ScalingConfigurationViewModelT _propertyChangedEvent; - - Animation::TransitionCollection _scalingModesListTransitions; - IObservableVector _scalingModes = single_threaded_observable_vector(); WinRTUtils::EventRevoker _scalingModeAddedRevoker; @@ -96,16 +81,14 @@ struct ScalingConfigurationViewModel : ScalingConfigurationViewModelT { +struct ScalingModesViewModel : ScalingModesViewModelT { }; } diff --git a/src/Magpie.App/ScalingConfigurationViewModel.idl b/src/Magpie.App/ScalingModesViewModel.idl similarity index 66% rename from src/Magpie.App/ScalingConfigurationViewModel.idl rename to src/Magpie.App/ScalingModesViewModel.idl index aab597138..608b03deb 100644 --- a/src/Magpie.App/ScalingConfigurationViewModel.idl +++ b/src/Magpie.App/ScalingModesViewModel.idl @@ -1,6 +1,6 @@ namespace Magpie.App { - runtimeclass ScalingConfigurationViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged { - ScalingConfigurationViewModel(); + runtimeclass ScalingModesViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged { + ScalingModesViewModel(); void Export(); void Import(); @@ -8,7 +8,6 @@ namespace Magpie.App { Boolean ShowErrorMessage; - Windows.UI.Xaml.Media.Animation.TransitionCollection ScalingModesListTransitions { get; }; IObservableVector ScalingModes { get; }; void PrepareForAdd(); diff --git a/src/Magpie.App/ScalingService.cpp b/src/Magpie.App/ScalingService.cpp index 9be94f3b7..938bcabf2 100644 --- a/src/Magpie.App/ScalingService.cpp +++ b/src/Magpie.App/ScalingService.cpp @@ -9,6 +9,7 @@ #include "Logger.h" #include "EffectsService.h" #include +#include "TouchHelper.h" using namespace ::Magpie::Core; using namespace winrt; @@ -53,7 +54,7 @@ void ScalingService::StartTimer() { _curCountdownSeconds = AppSettings::Get().CountdownSeconds(); _timerStartTimePoint = std::chrono::steady_clock::now(); _countDownTimer.Start(); - _isTimerOnChangedEvent(true); + IsTimerOnChanged.Invoke(true); } void ScalingService::StopTimer() { @@ -63,7 +64,7 @@ void ScalingService::StopTimer() { _curCountdownSeconds = 0; _countDownTimer.Stop(); - _isTimerOnChangedEvent(false); + IsTimerOnChanged.Invoke(false); } double ScalingService::SecondsLeft() const noexcept { @@ -98,7 +99,7 @@ void ScalingService::_WndToRestore(HWND value) { } _hwndToRestore = value; - _wndToRestoreChangedEvent(_hwndToRestore); + WndToRestoreChanged.Invoke(_hwndToRestore); } void ScalingService::_ShortcutService_ShortcutPressed(ShortcutAction action) { @@ -140,7 +141,7 @@ void ScalingService::_CountDownTimer_Tick(IInspectable const&, IInspectable cons return; } - _timerTickEvent(timeLeft); + TimerTick.Invoke(timeLeft); } fire_and_forget ScalingService::_CheckForegroundTimer_Tick(ThreadPoolTimer const& timer) { @@ -158,20 +159,29 @@ fire_and_forget ScalingService::_CheckForegroundTimer_Tick(ThreadPoolTimer const // ThreadPoolTimer 在后台线程触发 co_await _dispatcher; } - - const bool isAutoRestore = AppSettings::Get().IsAutoRestore(); - - const Profile& profile = ProfileService::Get().GetProfileForWindow(hwndFore); - // 自动恢复全屏或恢复记忆的窗口 - if ((profile.isAutoScale || (isAutoRestore && _hwndToRestore == hwndFore)) && - _CheckSrcWnd(hwndFore, _hwndToRestore != hwndFore) - ) { - _StartScale(hwndFore, profile); - co_return; - } - if (isAutoRestore && !_CheckSrcWnd(_hwndToRestore, false)) { + if (_hwndToRestore == hwndFore) { + // 检查自动恢复 + if (_CheckSrcWnd(hwndFore, false)) { + const Profile* profile = ProfileService::Get().GetProfileForWindow(hwndFore, false); + _StartScale(hwndFore, *profile); + co_return; + } + + // _hwndToRestore 无法缩放则清空 _WndToRestore(NULL); + } else { + // 检查自动缩放 + const Profile* profile = ProfileService::Get().GetProfileForWindow(hwndFore, true); + if (profile && _CheckSrcWnd(hwndFore, true)) { + _StartScale(hwndFore, *profile); + co_return; + } + + if (_hwndToRestore && !_CheckSrcWnd(_hwndToRestore, false)) { + // _hwndToRestore 无法缩放则清空 + _WndToRestore(NULL); + } } // 避免重复检查 @@ -210,7 +220,7 @@ fire_and_forget ScalingService::_ScalingRuntime_IsRunningChanged(bool isRunning) _CheckForegroundTimer_Tick(nullptr); } - _isRunningChangedEvent(isRunning); + IsRunningChanged.Invoke(isRunning); } bool ScalingService::_StartScale(HWND hWnd, const Profile& profile) { @@ -230,6 +240,13 @@ bool ScalingService::_StartScale(HWND hWnd, const Profile& profile) { } } } + + // 尝试启用触控支持 + bool isTouchSupportEnabled; + if (!TouchHelper::TryLaunchTouchHelper(isTouchSupportEnabled)) { + Logger::Get().Error("TryLaunchTouchHelper 失败"); + return false; + } options.graphicsCard = profile.graphicsCard; options.captureMethod = profile.captureMethod; @@ -238,7 +255,9 @@ bool ScalingService::_StartScale(HWND hWnd, const Profile& profile) { } options.multiMonitorUsage = profile.multiMonitorUsage; options.cursorInterpolationMode = profile.cursorInterpolationMode; - options.flags = profile.flags; + options.flags = profile.scalingFlags; + + options.IsTouchSupportEnabled(isTouchSupportEnabled); if (profile.isCroppingEnabled) { options.cropping = profile.cropping; @@ -306,7 +325,7 @@ void ScalingService::_ScaleForegroundWindow() { return; } - const Profile& profile = ProfileService::Get().GetProfileForWindow((HWND)hWnd); + const Profile& profile = *ProfileService::Get().GetProfileForWindow(hWnd, false); _StartScale(hWnd, profile); } @@ -317,32 +336,33 @@ static bool GetWindowIntegrityLevel(HWND hWnd, DWORD& integrityLevel) noexcept { return false; } - Win32Utils::ScopedHandle hProc(Win32Utils::SafeHandle( - OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, processId))); + wil::unique_process_handle hProc( + OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, processId)); if (!hProc) { Logger::Get().Win32Error("OpenProcess 失败"); return false; } - Win32Utils::ScopedHandle hQueryToken; - { - HANDLE token; - if (!OpenProcessToken(hProc.get(), TOKEN_QUERY, &token)) { - Logger::Get().Win32Error("OpenProcessToken 失败"); - return false; - } - hQueryToken.reset(token); + wil::unique_handle hQueryToken; + if (!OpenProcessToken(hProc.get(), TOKEN_QUERY, hQueryToken.put())) { + Logger::Get().Win32Error("OpenProcessToken 失败"); + return false; } return Win32Utils::GetProcessIntegrityLevel(hQueryToken.get(), integrityLevel); } bool ScalingService::_CheckSrcWnd(HWND hWnd, bool checkIL) noexcept { - if (!hWnd || !IsWindow(hWnd)) { + if (!hWnd || !IsWindowVisible(hWnd)) { + return false; + } + + // 不缩放不接受点击的窗口 + if (GetWindowLongPtr(hWnd, GWL_EXSTYLE) & WS_EX_TRANSPARENT) { return false; } - if (!WindowHelper::IsValidSrcWindow(hWnd)) { + if (WindowHelper::IsForbiddenSystemWindow(hWnd)) { return false; } diff --git a/src/Magpie.App/ScalingService.h b/src/Magpie.App/ScalingService.h index 5b533c116..ec8a0864f 100644 --- a/src/Magpie.App/ScalingService.h +++ b/src/Magpie.App/ScalingService.h @@ -32,83 +32,28 @@ class ScalingService { return _curCountdownSeconds > 0; } - event_token IsTimerOnChanged(delegate const& handler) { - return _isTimerOnChangedEvent.add(handler); - } - - WinRTUtils::EventRevoker IsTimerOnChanged(auto_revoke_t, delegate const& handler) { - event_token token = IsTimerOnChanged(handler); - return WinRTUtils::EventRevoker([this, token]() { - IsTimerOnChanged(token); - }); - } - - void IsTimerOnChanged(event_token const& token) { - _isTimerOnChangedEvent.remove(token); - } - double TimerProgress() const noexcept { return SecondsLeft() / _curCountdownSeconds; } double SecondsLeft() const noexcept; - event_token TimerTick(delegate const& handler) { - return _timerTickEvent.add(handler); - } - - WinRTUtils::EventRevoker TimerTick(auto_revoke_t, delegate const& handler) { - event_token token = TimerTick(handler); - return WinRTUtils::EventRevoker([this, token]() { - TimerTick(token); - }); - } - - void TimerTick(event_token const& token) { - _timerTickEvent.remove(token); - } - HWND WndToRestore() const noexcept { return _hwndToRestore; } - event_token WndToRestoreChanged(delegate const& handler) { - return _wndToRestoreChangedEvent.add(handler); - } - - WinRTUtils::EventRevoker WndToRestoreChanged(auto_revoke_t, delegate const& handler) { - event_token token = WndToRestoreChanged(handler); - return WinRTUtils::EventRevoker([this, token]() { - WndToRestoreChanged(token); - }); - } - - void WndToRestoreChanged(event_token const& token) { - _wndToRestoreChangedEvent.remove(token); - } - void ClearWndToRestore(); - event_token IsRunningChanged(delegate const& handler) { - return _isRunningChangedEvent.add(handler); - } - - WinRTUtils::EventRevoker IsRunningChanged(auto_revoke_t, delegate const& handler) { - event_token token = IsRunningChanged(handler); - return WinRTUtils::EventRevoker([this, token]() { - IsRunningChanged(token); - }); - } - - void IsRunningChanged(event_token const& token) { - _isRunningChangedEvent.remove(token); - } - bool IsRunning() const noexcept; // 强制重新检查前台窗口 void CheckForeground(); + WinRTUtils::Event> IsTimerOnChanged; + WinRTUtils::Event> TimerTick; + WinRTUtils::Event> WndToRestoreChanged; + WinRTUtils::Event> IsRunningChanged; + private: ScalingService() = default; @@ -147,11 +92,6 @@ class ScalingService { // 2. 用户使用热键退出全屏后暂时阻止该窗口自动放大 // 可能在线程池中访问,因此增加原子性 std::atomic _hwndChecked = NULL; - - event> _isTimerOnChangedEvent; - event> _timerTickEvent; - event> _wndToRestoreChangedEvent; - event> _isRunningChangedEvent; bool _isAutoScaling = false; }; diff --git a/src/Magpie.App/SettingsCard.cpp b/src/Magpie.App/SettingsCard.cpp index 7982180de..e8edeb832 100644 --- a/src/Magpie.App/SettingsCard.cpp +++ b/src/Magpie.App/SettingsCard.cpp @@ -250,7 +250,7 @@ void SettingsCard::_OnIsWrapEnabledChanged() const { auto trigger2 = GetTemplateChild(RightWrappedNoIconTrigger); if (trigger1 && trigger2) { - // CanTrigger 无法使用 TemplateBinding? + // CanTrigger 无法使用 TemplateBinding? const bool isWrapEnabled = IsWrapEnabled(); trigger1.as().CanTrigger(isWrapEnabled); trigger2.as().CanTrigger(isWrapEnabled); diff --git a/src/Magpie.App/SettingsExpander.Resource.xaml b/src/Magpie.App/SettingsExpander.Resource.xaml index c1f5af186..384a1677e 100644 --- a/src/Magpie.App/SettingsExpander.Resource.xaml +++ b/src/Magpie.App/SettingsExpander.Resource.xaml @@ -81,12 +81,7 @@ IsClickEnabled="False" /> - - - - - - + - + diff --git a/src/Magpie.App/SettingsExpander.cpp b/src/Magpie.App/SettingsExpander.cpp index beabf1602..f85413b3a 100644 --- a/src/Magpie.App/SettingsExpander.cpp +++ b/src/Magpie.App/SettingsExpander.cpp @@ -97,9 +97,9 @@ void SettingsExpander::_OnIsExpandedChanged(DependencyObject const& sender, Depe SettingsExpander* that = get_self(sender.as()); if (args.NewValue().as()) { - that->_expandedEvent(); + that->Expanded.Invoke(); } else { - that->_collapsedEvent(); + that->Collapsed.Invoke(); } } diff --git a/src/Magpie.App/SettingsExpander.h b/src/Magpie.App/SettingsExpander.h index 1bf255780..aff0f100c 100644 --- a/src/Magpie.App/SettingsExpander.h +++ b/src/Magpie.App/SettingsExpander.h @@ -1,5 +1,6 @@ #pragma once #include "SettingsExpander.g.h" +#include "WinRTUtils.h" namespace winrt::Magpie::App::implementation { @@ -38,12 +39,6 @@ struct SettingsExpander : SettingsExpanderT { bool IsExpanded() const { return GetValue(_isExpandedProperty).as(); } void IsExpanded(bool value) const { SetValue(_isExpandedProperty, box_value(value)); } - event_token Expanded(SignalDelegate const& handler) { return _expandedEvent.add(handler); } - void Expanded(winrt::event_token const& token) { _expandedEvent.remove(token); } - - event_token Collapsed(SignalDelegate const& handler) { return _collapsedEvent.add(handler); } - void Collapsed(winrt::event_token const& token) { _collapsedEvent.remove(token); } - IVector Items() const { return GetValue(_itemsProperty).as>(); } void Items(IVector const& value) const { SetValue(_itemsProperty, value); } @@ -55,6 +50,9 @@ struct SettingsExpander : SettingsExpanderT { void OnApplyTemplate(); + WinRTUtils::Event Expanded; + WinRTUtils::Event Collapsed; + private: static const DependencyProperty _headerProperty; static const DependencyProperty _descriptionProperty; @@ -71,9 +69,6 @@ struct SettingsExpander : SettingsExpanderT { static void _OnItemsConnectedPropertyChanged(DependencyObject const& sender, DependencyPropertyChangedEventArgs const&); void _OnItemsConnectedPropertyChanged(); - - event _expandedEvent; - event _collapsedEvent; }; } diff --git a/src/Magpie.App/SettingsPage.cpp b/src/Magpie.App/SettingsPage.cpp index 8b8aa9c80..19527a30f 100644 --- a/src/Magpie.App/SettingsPage.cpp +++ b/src/Magpie.App/SettingsPage.cpp @@ -3,9 +3,7 @@ #if __has_include("SettingsPage.g.cpp") #include "SettingsPage.g.cpp" #endif -#include "XamlUtils.h" #include "ComboBoxHelper.h" -#include "CommonSharedConstants.h" using namespace winrt; using namespace Windows::UI::Xaml::Input; @@ -16,13 +14,12 @@ void SettingsPage::InitializeComponent() { SettingsPageT::InitializeComponent(); ResourceLoader resourceLoader = - ResourceLoader::GetForCurrentView(CommonSharedConstants::APP_RESOURCE_MAP_ID); - hstring versionStr = resourceLoader. - GetString(L"ms-resource://Magpie.App/Microsoft.UI.Xaml/Resources/SettingsButtonName"); + ResourceLoader::GetForCurrentView(L"Microsoft.UI.Xaml/Resources"); + hstring versionStr = resourceLoader.GetString(L"SettingsButtonName"); SettingsPageFrame().Title(versionStr); } -void SettingsPage::ComboBox_DropDownOpened(IInspectable const& sender, IInspectable const&) { +void SettingsPage::ComboBox_DropDownOpened(IInspectable const& sender, IInspectable const&) const { ComboBoxHelper::DropDownOpened(*this, sender); } diff --git a/src/Magpie.App/SettingsPage.h b/src/Magpie.App/SettingsPage.h index d12feba54..b8bbe0833 100644 --- a/src/Magpie.App/SettingsPage.h +++ b/src/Magpie.App/SettingsPage.h @@ -10,7 +10,7 @@ struct SettingsPage : SettingsPageT { return _viewModel; } - void ComboBox_DropDownOpened(IInspectable const& sender, IInspectable const&); + void ComboBox_DropDownOpened(IInspectable const& sender, IInspectable const&) const; private: Magpie::App::SettingsViewModel _viewModel; diff --git a/src/Magpie.App/SettingsPage.xaml b/src/Magpie.App/SettingsPage.xaml index 7f183786c..2d5af79c2 100644 --- a/src/Magpie.App/SettingsPage.xaml +++ b/src/Magpie.App/SettingsPage.xaml @@ -21,19 +21,16 @@ ItemsSource="{x:Bind ViewModel.Languages, Mode=OneTime}" SelectedIndex="{x:Bind ViewModel.Language, Mode=TwoWay}" /> - - - -