Compare commits

...

42 Commits

Author SHA1 Message Date
LeapwardKoex a68aa187c7 Add tooltips
.NET Core Desktop / build (Release) (push) Has been cancelled Details
Improve touch pannel button styling
Enforce aspect ratio resizing for touch panel and a minimum size
2025-04-07 22:00:06 +12:00
LeapwardKoex a2b63c607a Add translations for ring button size label
.NET Core Desktop / build (Release) (push) Has been cancelled Details
2025-04-06 10:23:54 +12:00
LeapwardKoex 76f39d5be8 Merge branch 'master' of https://github.com/Leapward-Koex/MaiTouchSensorEmulator 2025-04-06 10:04:16 +12:00
LeapwardKoex 643fb31505 Add support for large button setting, which extends the size of the ring buttons to fill the rendered area of the touch pannel
Re-write button handling to decrese latency and fix issues where some notes where not picked up during slide actions
2025-04-06 10:04:13 +12:00
LeapwardKoex df962b7304 Simplify button polygons and shrink gaps 2025-04-03 21:43:29 +13:00
Leapward-Koex 289b4f2ff5
Replace actions/upload-artifact@v3 with actions/upload-artifact@v4
.NET Core Desktop / build (Release) (push) Has been cancelled Details
2025-03-15 22:43:28 +13:00
LeapwardKoex f12494faf3 Handle potentially invalid received serial commands without crashing 2025-03-15 22:40:38 +13:00
LeapwardKoex 6e8500a479 Revert back to 1.5
.NET Core Desktop / build (Release) (push) Has been cancelled Details
2024-11-19 21:31:22 +13:00
Leapward-Koex 79e2ea3fcd
Update issue templates 2024-11-19 21:08:08 +13:00
Leapward-Koex 0815b20ee8
Update issue templates 2024-11-19 21:05:44 +13:00
LeapwardKoex f39fead973 Add migrating user settings for installer 2024-11-19 20:46:40 +13:00
LeapwardKoex f239e9b534 Revert version changes 2024-11-19 20:35:02 +13:00
LeapwardKoex f75d77c66a Improve versioning in actions file 2024-11-19 20:29:51 +13:00
LeapwardKoex 3571776b83 Add about section and button to open log folder
.NET Core Desktop / build (Release) (push) Has been cancelled Details
2024-11-16 17:26:10 +13:00
LeapwardKoex 0347c983b6 Update installer to include wpfui 2024-11-16 16:43:09 +13:00
LeapwardKoex 066ecf6850 Redesign main window to be winui3 themed 2024-11-16 16:23:29 +13:00
LeapwardKoex 3eb9c1438a Include some other useful files
.NET Core Desktop / build (Release) (push) Has been cancelled Details
2024-11-15 21:13:37 +13:00
LeapwardKoex 179a6a256a Try a different path
.NET Core Desktop / build (Release) (push) Has been cancelled Details
2024-11-14 22:30:52 +13:00
LeapwardKoex 3f8236506a Try building msi from github action 2024-11-14 22:28:15 +13:00
LeapwardKoex 4d36fc7c64 Installer improvements 2024-11-14 22:19:07 +13:00
LeapwardKoex 81edbde5cc Add ability to create installer using Advanced Installer 2024-11-14 21:40:30 +13:00
LeapwardKoex 3a8d0e59c4 Add ability to add a custom border or rainbow border to the surrounding touch sensor input area
.NET Core Desktop / build (Release) (push) Has been cancelled Details
2024-11-09 22:40:55 +13:00
LeapwardKoex a01cdd6dfa Fix calculation for automatic positioning and rendering scales 2024-11-09 20:30:58 +13:00
LeapwardKoex 9d06a0293a Make touch panel 16:9 so the touch panel always consumes touch inputs 2024-11-09 16:52:02 +13:00
LeapwardKoex ee47b30a90 All minimizing of main window
.NET Core Desktop / build (Release) (push) Has been cancelled Details
2024-11-09 11:32:15 +13:00
LeapwardKoex 3b7aa9c11e Tidy up files 2024-11-09 11:30:47 +13:00
LeapwardKoex a0cfa9c0c6 Automatically hide main settings window when sensor is repositioned. 2024-11-09 11:24:09 +13:00
LeapwardKoex 2033b5e145 Update videos in other readme
.NET Core Desktop / build (Release) (push) Has been cancelled Details
2024-09-07 12:19:28 +12:00
LeapwardKoex a0364ef46b Merge branch 'master' of https://github.com/Leapward-Koex/MaiTouchSensorEmulator 2024-09-07 12:18:15 +12:00
LeapwardKoex 7d67cb9437 Update video assets 2024-09-07 12:18:13 +12:00
Leapward-Koex 4bd11f9f25
Update README.md with better video codec 2024-09-07 12:17:12 +12:00
Leapward-Koex c24e4a47a2
Merge pull request #7 from MoeclubM/master
.NET Core Desktop / build (Release) (push) Has been cancelled Details
Fix typo
2024-06-20 17:12:53 +12:00
Moeclub 9c1057dbc5
Fix typo 2024-06-19 22:58:54 +08:00
LeapwardKoex 06edb9fcfc Add application icon
.NET Core Desktop / build (Release) (push) Has been cancelled Details
2024-06-17 21:48:43 +12:00
LeapwardKoex 621f90937f Add readme video assets
.NET Core Desktop / build (Release) (push) Has been cancelled Details
2024-06-15 15:57:17 +12:00
LeapwardKoex b6872dde34 Try and fix phantom keyboard presses when using keyboard emulation
.NET Core Desktop / build (Release) (push) Has been cancelled Details
Change ring button emulation to be defaulted to true
2024-06-12 22:50:48 +12:00
LeapwardKoex 48cdfd0e72 Add ability for emulator to emulate keyboard events for ring button emulation 2024-06-12 22:37:09 +12:00
LeapwardKoex b73e88f1da Add multilanguage readme 2024-04-24 23:12:45 +12:00
LeapwardKoex fef5fd0ebd Add japanese language support 2024-04-24 22:53:05 +12:00
LeapwardKoex f42f210452 Translate remaining text 2024-04-24 22:49:02 +12:00
LeapwardKoex 851d83ca51 Add initial multi language support 2024-04-24 21:40:10 +12:00
LeapwardKoex 0a79a71f82 Remove now unused images 2024-04-24 21:27:30 +12:00
71 changed files with 3461 additions and 472 deletions

View File

@ -0,0 +1,23 @@
---
name: Bug report
about: 'Create a report to help improve the application '
title: ''
labels: ''
assignees: ''
---
**Please describe the issue in English if possible**
**Describe the bug**
A clear and concise description of what the bug is and when it occurs. Please include the app version and any logs/crash dumps.
For the portable version of the application these can be found in the same directory as the exe.
For installed version of the application these can be found under `%LOCALAPPDATA%/WpfMaiTouchEmulator`
**To Reproduce**
**Screenshots**
If applicable, add screenshots/videos to help explain your problem.
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,19 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Please fill out in English if possible**
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -1,41 +1,3 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build, test, sign and package a WPF or Windows Forms desktop application
# built on .NET Core.
# To learn how to migrate your existing application to .NET Core,
# refer to https://docs.microsoft.com/en-us/dotnet/desktop-wpf/migration/convert-project-from-net-framework
#
# To configure this workflow:
#
# 1. Configure environment variables
# GitHub sets default environment variables for every workflow run.
# Replace the variables relative to your project in the "env" section below.
#
# 2. Signing
# Generate a signing certificate in the Windows Application
# Packaging Project or add an existing signing certificate to the project.
# Next, use PowerShell to encode the .pfx file using Base64 encoding
# by running the following Powershell script to generate the output string:
#
# $pfx_cert = Get-Content '.\SigningCertificate.pfx' -Encoding Byte
# [System.Convert]::ToBase64String($pfx_cert) | Out-File 'SigningCertificate_Encoded.txt'
#
# Open the output file, SigningCertificate_Encoded.txt, and copy the
# string inside. Then, add the string to the repo as a GitHub secret
# and name it "Base64_Encoded_Pfx."
# For more information on how to configure your signing certificate for
# this workflow, refer to https://github.com/microsoft/github-actions-for-desktop-apps#signing
#
# Finally, add the signing certificate password to the repo as a secret and name it "Pfx_Key".
# See "Build the Windows Application Packaging project" below to see how the secret is used.
#
# For more information on GitHub Actions, refer to https://github.com/features/actions
# For a complete CI/CD sample to get started with GitHub Action workflows for Desktop Applications,
# refer to https://github.com/microsoft/github-actions-for-desktop-apps
name: .NET Core Desktop
on:
@ -53,13 +15,11 @@ jobs:
matrix:
configuration: [Release]
runs-on: windows-latest # For a list of available runner types, refer to
# https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on
runs-on: windows-latest
env:
Solution_Name: WpfMaiTouchEmulator.sln # Replace with your solution name, i.e. MyWpfApp.sln.
Wap_Project_Directory: . # Replace with the Wap project directory relative to the solution, i.e. MyWpfApp.Package.
Wap_Project_Path: WpfMaiTouchEmulator.csproj # Replace with the path to your Wap project, i.e. MyWpf.App.Package\MyWpfApp.Package.wapproj.
Solution_Name: WpfMaiTouchEmulator.sln
Wap_Project_Directory: .
Wap_Project_Path: WpfMaiTouchEmulator.csproj
steps:
- name: Checkout
@ -92,20 +52,38 @@ jobs:
# Create the app package by building and packaging the Windows Application Packaging project
- name: Create the app package
run: msbuild $env:Wap_Project_Path /p:Configuration=$env:Configuration /p:UapAppxPackageBuildMode=$env:Appx_Package_Build_Mode /p:AppxBundle=$env:Appx_Bundle /p:PackageCertificateKeyFile=GitHubActionsWorkflow.pfx /p:PackageCertificatePassword=${{ secrets.Pfx_Key }}
run: msbuild $env:Wap_Project_Path /p:Configuration=$env:Configuration /p:UapAppxPackageBuildMode=$env:Appx_Package_Build_Mode /p:AppxBundle=$env:Appx_Bundle /p:PackageCertificateKeyFile=GitHubActionsWorkflow.pfx /p:PackageCertificatePassword=${{ secrets.Pfx_Key }} /p:Version=1.5.0.${{ github.run_number }}
env:
Appx_Bundle: Always
Appx_Bundle_Platforms: x86|x64
Appx_Package_Build_Mode: StoreUpload
Configuration: ${{ matrix.configuration }}
- name: Build AIP
uses: caphyon/advinst-github-action@v2.0
with:
advinst-version: '22.2'
advinst-enable-automation: 'true'
aip-path: ${{ github.workspace }}\Installer\Mai Touch Emulator.aip
aip-build-name: DefaultBuild
aip-package-name: installMaiTouchEmulator.msi
aip-output-dir: ${{ github.workspace }}\setup
aip-commands: |
SetProperty FOO="foo"
SetVersion 1.5.0.${{ github.run_number }}
# Remove the pfx
- name: Remove the pfx
run: Remove-Item -path $env:Wap_Project_Directory\GitHubActionsWorkflow.pfx
# Upload the MSIX package: https://github.com/marketplace/actions/upload-a-build-artifact
- name: Upload build artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Emulator package
path: ./bin/Release/net8.0-windows/
name: MaiTouchEmulatorPortable
path: ./bin/Release/net8.0-windows10.0.17763.0/
- name: Publish setup artifact
uses: actions/upload-artifact@v4
with:
name: MaiTouchEmulatorInstaller
path: ./setup/installMaiTouchEmulator.msi

4
.gitignore vendored
View File

@ -360,4 +360,6 @@ MigrationBackup/
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
FodyWeavers.xsd
Installer/

View File

@ -22,6 +22,24 @@
<setting name="FirstOpen" serializeAs="String">
<value>True</value>
</setting>
<setting name="UserLanguage" serializeAs="String">
<value />
</setting>
<setting name="IsRingButtonEmulationEnabled" serializeAs="String">
<value>True</value>
</setting>
<setting name="BorderSetting" serializeAs="String">
<value>0</value>
</setting>
<setting name="BorderColour" serializeAs="String">
<value />
</setting>
<setting name="UserSettingsUpgradeRequired" serializeAs="String">
<value>True</value>
</setting>
<setting name="IsLargeButtonsEnabled" serializeAs="String">
<value>False</value>
</setting>
</WpfMaiTouchEmulator.Properties.Settings>
</userSettings>
</configuration>

View File

@ -2,8 +2,14 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfMaiTouchEmulator"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ui:ThemesDictionary Theme="Light" />
<ui:ControlsDictionary />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@ -28,7 +28,7 @@ public partial class App : Application
private void CreateDump(Exception exception)
{
var dumpFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "CrashDump.dmp");
var dumpFilePath = Path.Combine(Logger.GetLogPath(), "CrashDump.dmp");
using (var fs = new FileStream(dumpFilePath, FileMode.Create))
{

Binary file not shown.

Before

Width:  |  Height:  |  Size: 724 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 777 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 697 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 731 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 732 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 783 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 412 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 412 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 396 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 717 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

BIN
Assets/icon.ico 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

BIN
Assets/icon.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,287 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<DOCUMENT Type="Advanced Installer" CreateVersion="22.2" version="22.2" Modules="simple" RootPath="." Language="en" Id="{B5359581-6E04-4DC3-88F2-8DB4D3AD09E6}">
<COMPONENT cid="caphyon.advinst.msicomp.MsiPropsComponent">
<ROW Property="AI_APP_FILE" Value="[#WpfMaiTouchEmulator.exe]"/>
<ROW Property="AI_BITMAP_DISPLAY_MODE" Value="0"/>
<ROW Property="AI_PREDEF_LCONDS_PROPS" Value="AI_DETECTED_ADMIN_USER;AI_DETECTED_DOTNET_CORE_VERSION"/>
<ROW Property="AI_REQUIRED_DOTNET_CORE_DISPLAY" MultiBuildValue="DefaultBuild:8.0" ValueLocId="-"/>
<ROW Property="AI_REQUIRED_DOTNET_CORE_VERSION" MultiBuildValue="DefaultBuild:8.0" ValueLocId="-"/>
<ROW Property="ALLUSERS" Value="1" MultiBuildValue="DefaultBuild:2"/>
<ROW Property="ARPCOMMENTS" Value="This installer database contains the logic and data required to install [|ProductName]." ValueLocId="*"/>
<ROW Property="ARPPRODUCTICON" Value="icon.exe" Type="8"/>
<ROW Property="CTRLS" Value="2"/>
<ROW Property="Manufacturer" Value="Leapward-Koex"/>
<ROW Property="ProductCode" Value="1033:{08E138ED-9CC0-4CF8-BC4A-E0B7B270D5F7} " Type="16"/>
<ROW Property="ProductLanguage" Value="1033"/>
<ROW Property="ProductName" Value="Mai Touch Emulator"/>
<ROW Property="ProductVersion" Value="1.0.0" Options="32"/>
<ROW Property="REBOOT" MultiBuildValue="DefaultBuild:ReallySuppress"/>
<ROW Property="SecureCustomProperties" Value="OLDPRODUCTS;AI_NEWERPRODUCTFOUND"/>
<ROW Property="UpgradeCode" Value="{464CA79D-14BC-45BC-AE26-F5C4D59346C9}"/>
<ROW Property="WindowsType9X" MultiBuildValue="DefaultBuild:Windows 9x/ME" ValueLocId="-"/>
<ROW Property="WindowsType9XDisplay" MultiBuildValue="DefaultBuild:Windows 9x/ME" ValueLocId="-"/>
<ROW Property="WindowsTypeNT" MultiBuildValue="DefaultBuild:Windows 7 x86, Windows 8 x86, Windows 8.1 x86" ValueLocId="-"/>
<ROW Property="WindowsTypeNT40" MultiBuildValue="DefaultBuild:Windows NT 4.0" ValueLocId="-"/>
<ROW Property="WindowsTypeNT40Display" MultiBuildValue="DefaultBuild:Windows NT 4.0" ValueLocId="-"/>
<ROW Property="WindowsTypeNT50" MultiBuildValue="DefaultBuild:Windows 2000" ValueLocId="-"/>
<ROW Property="WindowsTypeNT50Display" MultiBuildValue="DefaultBuild:Windows 2000" ValueLocId="-"/>
<ROW Property="WindowsTypeNT5X" MultiBuildValue="DefaultBuild:Windows XP/2003" ValueLocId="-"/>
<ROW Property="WindowsTypeNT5XDisplay" MultiBuildValue="DefaultBuild:Windows XP/2003" ValueLocId="-"/>
<ROW Property="WindowsTypeNT60" MultiBuildValue="DefaultBuild:Windows Vista/Server 2008" ValueLocId="-"/>
<ROW Property="WindowsTypeNT60Display" MultiBuildValue="DefaultBuild:Windows Vista/Server 2008" ValueLocId="-"/>
<ROW Property="WindowsTypeNT64" MultiBuildValue="DefaultBuild:Windows 7 x64, Windows Server 2008 R2 x64, Windows 8 x64, Windows Server 2012 x64, Windows 8.1 x64, Windows Server 2012 R2 x64, Windows Server x64" ValueLocId="-"/>
<ROW Property="WindowsTypeNT64Display" MultiBuildValue="DefaultBuild:Windows 7 x64, Windows Server 2008 R2 x64, Windows 8 x64, Windows Server 2012 x64, Windows 8.1 x64, Windows Server 2012 R2 x64, Windows Server x64" ValueLocId="-"/>
<ROW Property="WindowsTypeNTDisplay" MultiBuildValue="DefaultBuild:Windows 7 x86, Windows 8 x86, Windows 8.1 x86" ValueLocId="-"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiDirsComponent">
<ROW Directory="APPDIR" Directory_Parent="TARGETDIR" DefaultDir="APPDIR:." IsPseudoRoot="1"/>
<ROW Directory="ProgramMenuFolder" Directory_Parent="TARGETDIR" DefaultDir="PROGRA~1|ProgramMenuFolder" IsPseudoRoot="1"/>
<ROW Directory="TARGETDIR" DefaultDir="SourceDir"/>
<ROW Directory="com0com_Dir" Directory_Parent="thirdpartyprograms_Dir" DefaultDir="com0com"/>
<ROW Directory="jaJP_Dir" Directory_Parent="APPDIR" DefaultDir="ja-JP"/>
<ROW Directory="lib_1_Dir" Directory_Parent="win_Dir" DefaultDir="lib"/>
<ROW Directory="net8.0_1_Dir" Directory_Parent="lib_1_Dir" DefaultDir="net8.0"/>
<ROW Directory="runtimes_Dir" Directory_Parent="APPDIR" DefaultDir="runtimes"/>
<ROW Directory="thirdpartyprograms_Dir" Directory_Parent="APPDIR" DefaultDir="THIRDP~1|thirdparty programs"/>
<ROW Directory="win_Dir" Directory_Parent="runtimes_Dir" DefaultDir="win"/>
<ROW Directory="zhCN_Dir" Directory_Parent="APPDIR" DefaultDir="zh-CN"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.SideBySideGuidComponent">
<ROW Component="APPDIR" Value="{E9523757-B9AF-4FE4-8034-0CA60B965037}"/>
<ROW Component="ProductInformation" Value="{69562BAB-9822-4EC6-BB86-EA5953730D68}"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiCompsComponent">
<ROW Component="AI_INSTALLPERUSER" ComponentId="{8DE0F484-4802-477C-8708-40D881BE6323}" Directory_="APPDIR" Attributes="260" KeyPath="AI_INSTALLPERUSER" Options="1"/>
<ROW Component="APPDIR" ComponentId="{E9523757-B9AF-4FE4-8034-0CA60B965037}" Directory_="APPDIR" Attributes="0"/>
<ROW Component="Microsoft.Win32.SystemEvents.dll" ComponentId="{1CD260D3-BC38-4E6F-87B3-F243BF6FB3E5}" Directory_="net8.0_1_Dir" Attributes="0" KeyPath="Microsoft.Win32.SystemEvents.dll"/>
<ROW Component="Microsoft.Win32.SystemEvents.dll_1" ComponentId="{4FF055E5-90A5-4D0B-869D-81D87D9F5D4C}" Directory_="APPDIR" Attributes="0" KeyPath="Microsoft.Win32.SystemEvents.dll_1"/>
<ROW Component="Microsoft.Windows.SDK.NET.dll" ComponentId="{FD3FCBF4-F9D9-4DD6-BDB1-2E462A39154E}" Directory_="APPDIR" Attributes="0" KeyPath="Microsoft.Windows.SDK.NET.dll"/>
<ROW Component="ProductInformation" ComponentId="{69562BAB-9822-4EC6-BB86-EA5953730D68}" Directory_="APPDIR" Attributes="260" KeyPath="Version"/>
<ROW Component="System.Drawing.Common.dll" ComponentId="{27EDF0B3-615B-49A8-B181-7339D01DC5BD}" Directory_="APPDIR" Attributes="0" KeyPath="System.Drawing.Common.dll"/>
<ROW Component="System.IO.Ports.dll" ComponentId="{D776A384-99ED-49DE-8047-4C06A5141960}" Directory_="APPDIR" Attributes="0" KeyPath="System.IO.Ports.dll"/>
<ROW Component="System.IO.Ports.dll_1" ComponentId="{767812B6-6569-4A50-917D-F7FAB0DEA8FB}" Directory_="net8.0_1_Dir" Attributes="0" KeyPath="System.IO.Ports.dll_1"/>
<ROW Component="System.Private.Windows.Core.dll" ComponentId="{55D5CCEF-9774-4702-950A-F564146D2473}" Directory_="APPDIR" Attributes="0" KeyPath="System.Private.Windows.Core.dll"/>
<ROW Component="WinRT.Runtime.dll" ComponentId="{8BEB0AE9-88A8-409E-9129-0A1F2CBE0850}" Directory_="APPDIR" Attributes="0" KeyPath="WinRT.Runtime.dll"/>
<ROW Component="Wpf.Ui.dll" ComponentId="{8A193511-50C0-4369-8159-FF599D4CEEB8}" Directory_="APPDIR" Attributes="0" KeyPath="Wpf.Ui.dll"/>
<ROW Component="WpfMaiTouchEmulator.deps.json" ComponentId="{CD8C5345-8556-4C3E-AAE9-1E7D90E1DD5A}" Directory_="APPDIR" Attributes="0" KeyPath="WpfMaiTouchEmulator.deps.json" Type="0"/>
<ROW Component="WpfMaiTouchEmulator.dll" ComponentId="{B289ACCE-2BAF-436F-814B-D39E91524B89}" Directory_="APPDIR" Attributes="256" KeyPath="WpfMaiTouchEmulator.dll"/>
<ROW Component="WpfMaiTouchEmulator.exe" ComponentId="{AA079BFA-401B-4AFA-A56A-92F2A65DB096}" Directory_="APPDIR" Attributes="256" KeyPath="WpfMaiTouchEmulator.exe"/>
<ROW Component="WpfMaiTouchEmulator.resources.dll" ComponentId="{01F7375B-D27D-407C-AD17-EE73D183740C}" Directory_="zhCN_Dir" Attributes="256" KeyPath="WpfMaiTouchEmulator.resources.dll"/>
<ROW Component="WpfMaiTouchEmulator.resources.dll_1" ComponentId="{27FEB25A-02E1-4D0B-9357-FE31FC757C34}" Directory_="jaJP_Dir" Attributes="256" KeyPath="WpfMaiTouchEmulator.resources.dll_1"/>
<ROW Component="com0com.cat" ComponentId="{DE7A59B3-AA91-441B-8E80-99E5B7B56F07}" Directory_="com0com_Dir" Attributes="0" KeyPath="com0com.cat" Type="0"/>
<ROW Component="setup.dll" ComponentId="{6DB50EDF-6F89-4A1D-A709-584F83A0B5CF}" Directory_="com0com_Dir" Attributes="256" KeyPath="setup.dll"/>
<ROW Component="setupc.exe" ComponentId="{EEA73D45-D23D-4F40-A657-F93D96427FDF}" Directory_="com0com_Dir" Attributes="256" KeyPath="setupc.exe"/>
<ROW Component="setupg.exe" ComponentId="{8532B8AB-C539-4A78-A83A-FF8BC1671E15}" Directory_="com0com_Dir" Attributes="0" KeyPath="setupg.exe"/>
<ROW Component="uninstall.exe" ComponentId="{645968DC-E43B-43A0-B6A6-A25616A43837}" Directory_="com0com_Dir" Attributes="0" KeyPath="uninstall.exe"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiFeatsComponent">
<ROW Feature="MainFeature" Title="MainFeature" Description="Description" Display="1" Level="1" Directory_="APPDIR" Attributes="0"/>
<ATTRIBUTE name="CurrentFeature" value="MainFeature"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiFilesComponent">
<ROW File="com0com.cat" Component_="com0com.cat" FileName="com0com.cat" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\thirdparty programs\com0com\com0com.cat" SelfReg="false"/>
<ROW File="com0com.inf" Component_="com0com.cat" FileName="com0com.inf" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\thirdparty programs\com0com\com0com.inf" SelfReg="false"/>
<ROW File="com0com.sys" Component_="com0com.cat" FileName="com0com.sys" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\thirdparty programs\com0com\com0com.sys" SelfReg="false"/>
<ROW File="ReadMe.txt" Component_="com0com.cat" FileName="ReadMe.txt" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\thirdparty programs\com0com\ReadMe.txt" SelfReg="false"/>
<ROW File="setup.dll" Component_="setup.dll" FileName="setup.dll" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\thirdparty programs\com0com\setup.dll" SelfReg="false"/>
<ROW File="setupc.exe" Component_="setupc.exe" FileName="setupc.exe" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\thirdparty programs\com0com\setupc.exe" SelfReg="false" DigSign="true"/>
<ROW File="setupg.exe" Component_="setupg.exe" FileName="setupg.exe" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\thirdparty programs\com0com\setupg.exe" SelfReg="false" DigSign="true"/>
<ROW File="uninstall.exe" Component_="uninstall.exe" FileName="UNINST~1.EXE|uninstall.exe" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\thirdparty programs\com0com\uninstall.exe" SelfReg="false" DigSign="true"/>
<ROW File="Microsoft.Win32.SystemEvents.dll" Component_="Microsoft.Win32.SystemEvents.dll" FileName="MICROS~1.DLL|Microsoft.Win32.SystemEvents.dll" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\runtimes\win\lib\net8.0\Microsoft.Win32.SystemEvents.dll" SelfReg="false"/>
<ROW File="System.IO.Ports.dll_1" Component_="System.IO.Ports.dll_1" FileName="SYSTEM~1.DLL|System.IO.Ports.dll" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\runtimes\win\lib\net8.0\System.IO.Ports.dll" SelfReg="false"/>
<ROW File="WpfMaiTouchEmulator.resources.dll" Component_="WpfMaiTouchEmulator.resources.dll" FileName="WPFMAI~1.DLL|WpfMaiTouchEmulator.resources.dll" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\zh-CN\WpfMaiTouchEmulator.resources.dll" SelfReg="false"/>
<ROW File="WpfMaiTouchEmulator.resources.dll_1" Component_="WpfMaiTouchEmulator.resources.dll_1" FileName="WPFMAI~1.DLL|WpfMaiTouchEmulator.resources.dll" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\ja-JP\WpfMaiTouchEmulator.resources.dll" SelfReg="false"/>
<ROW File="Microsoft.Win32.SystemEvents.dll_1" Component_="Microsoft.Win32.SystemEvents.dll_1" FileName="MICROS~1.DLL|Microsoft.Win32.SystemEvents.dll" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\Microsoft.Win32.SystemEvents.dll" SelfReg="false"/>
<ROW File="Microsoft.Windows.SDK.NET.dll" Component_="Microsoft.Windows.SDK.NET.dll" FileName="MICROS~2.DLL|Microsoft.Windows.SDK.NET.dll" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\Microsoft.Windows.SDK.NET.dll" SelfReg="false"/>
<ROW File="System.Drawing.Common.dll" Component_="System.Drawing.Common.dll" FileName="SYSTEM~1.DLL|System.Drawing.Common.dll" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\System.Drawing.Common.dll" SelfReg="false"/>
<ROW File="System.IO.Ports.dll" Component_="System.IO.Ports.dll" FileName="SYSTEM~2.DLL|System.IO.Ports.dll" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\System.IO.Ports.dll" SelfReg="false"/>
<ROW File="System.Private.Windows.Core.dll" Component_="System.Private.Windows.Core.dll" FileName="SYSTEM~3.DLL|System.Private.Windows.Core.dll" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\System.Private.Windows.Core.dll" SelfReg="false"/>
<ROW File="WinRT.Runtime.dll" Component_="WinRT.Runtime.dll" FileName="WINRTR~1.DLL|WinRT.Runtime.dll" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\WinRT.Runtime.dll" SelfReg="false"/>
<ROW File="WpfMaiTouchEmulator.deps.json" Component_="WpfMaiTouchEmulator.deps.json" FileName="WPFMAI~1.JSO|WpfMaiTouchEmulator.deps.json" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\WpfMaiTouchEmulator.deps.json" SelfReg="false"/>
<ROW File="WpfMaiTouchEmulator.dll" Component_="WpfMaiTouchEmulator.dll" FileName="WPFMAI~1.DLL|WpfMaiTouchEmulator.dll" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\WpfMaiTouchEmulator.dll" SelfReg="false"/>
<ROW File="WpfMaiTouchEmulator.dll.config" Component_="WpfMaiTouchEmulator.deps.json" FileName="WPFMAI~1.CON|WpfMaiTouchEmulator.dll.config" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\WpfMaiTouchEmulator.dll.config" SelfReg="false"/>
<ROW File="WpfMaiTouchEmulator.exe" Component_="WpfMaiTouchEmulator.exe" FileName="WPFMAI~1.EXE|WpfMaiTouchEmulator.exe" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\WpfMaiTouchEmulator.exe" SelfReg="false" DigSign="true"/>
<ROW File="WpfMaiTouchEmulator.runtimeconfig.json" Component_="WpfMaiTouchEmulator.deps.json" FileName="WPFMAI~2.JSO|WpfMaiTouchEmulator.runtimeconfig.json" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\WpfMaiTouchEmulator.runtimeconfig.json" SelfReg="false"/>
<ROW File="Wpf.Ui.dll" Component_="Wpf.Ui.dll" FileName="WPFUI~1.DLL|Wpf.Ui.dll" Attributes="0" SourcePath="..\bin\Release\net8.0-windows10.0.17763.0\Wpf.Ui.dll" SelfReg="false"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.AiPersistentPropComponent">
<ROW Property="AI_INSTALLPERUSER" Registry="AI_INSTALLPERUSER" HklmSearch="AI_INSTALLPERUSER" HkcuSearch="AI_INSTALLPERUSER_1" Hklm64Search="AI_INSTALLPERUSER_2" Hkcu64Search="AI_INSTALLPERUSER_3"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.BootstrapperUISequenceComponent">
<ROW Action="AI_DetectSoftware" Sequence="151"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.BuildComponent">
<ROW BuildKey="DefaultBuild" BuildName="DefaultBuild" BuildOrder="1" BuildType="0" Languages="en" InstallationType="4" SummInfoMetadata="Page Count:450" UseLargeSchema="true" MsiPackageType="x64"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.DictionaryComponent">
<ROW Path="&lt;AI_DICTS&gt;ui.ail"/>
<ROW Path="&lt;AI_DICTS&gt;ui_en.ail"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.FragmentComponent">
<ROW Fragment="CommonUI.aip" Path="&lt;AI_FRAGS&gt;CommonUI.aip"/>
<ROW Fragment="FolderDlg.aip" Path="&lt;AI_THEMES&gt;classic\fragments\FolderDlg.aip"/>
<ROW Fragment="InstallTypeDlg.aip" Path="&lt;AI_THEMES&gt;classic\fragments\InstallTypeDlg.aip"/>
<ROW Fragment="MaintenanceTypeDlg.aip" Path="&lt;AI_THEMES&gt;classic\fragments\MaintenanceTypeDlg.aip"/>
<ROW Fragment="MaintenanceWelcomeDlg.aip" Path="&lt;AI_THEMES&gt;classic\fragments\MaintenanceWelcomeDlg.aip"/>
<ROW Fragment="SequenceDialogs.aip" Path="&lt;AI_THEMES&gt;classic\fragments\SequenceDialogs.aip"/>
<ROW Fragment="Sequences.aip" Path="&lt;AI_FRAGS&gt;Sequences.aip"/>
<ROW Fragment="StaticUIStrings.aip" Path="&lt;AI_FRAGS&gt;StaticUIStrings.aip"/>
<ROW Fragment="Themes.aip" Path="&lt;AI_FRAGS&gt;Themes.aip"/>
<ROW Fragment="UI.aip" Path="&lt;AI_THEMES&gt;classic\fragments\UI.aip"/>
<ROW Fragment="Validation.aip" Path="&lt;AI_FRAGS&gt;Validation.aip"/>
<ROW Fragment="VerifyRemoveDlg.aip" Path="&lt;AI_THEMES&gt;classic\fragments\VerifyRemoveDlg.aip"/>
<ROW Fragment="VerifyRepairDlg.aip" Path="&lt;AI_THEMES&gt;classic\fragments\VerifyRepairDlg.aip"/>
<ROW Fragment="WelcomeDlg.aip" Path="&lt;AI_THEMES&gt;classic\fragments\WelcomeDlg.aip"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiAppSearchComponent">
<ROW Property="AI_INSTALLPERUSER" Signature_="AI_INSTALLPERUSER"/>
<ROW Property="AI_INSTALLPERUSER" Signature_="AI_INSTALLPERUSER_1"/>
<ROW Property="AI_INSTALLPERUSER" Signature_="AI_INSTALLPERUSER_2"/>
<ROW Property="AI_INSTALLPERUSER" Signature_="AI_INSTALLPERUSER_3"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiBinaryComponent">
<ROW Name="ShortcutFlags.dll" SourcePath="&lt;AI_CUSTACTS&gt;ShortcutFlags.dll"/>
<ROW Name="SoftwareDetector.dll" SourcePath="&lt;AI_CUSTACTS&gt;SoftwareDetector.dll"/>
<ROW Name="aicustact.dll" SourcePath="&lt;AI_CUSTACTS&gt;aicustact.dll"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiControlEventComponent">
<ROW Dialog_="WelcomeDlg" Control_="Next" Event="NewDialog" Argument="InstallTypeDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="FolderDlg" Control_="Next" Event="NewDialog" Argument="VerifyReadyDlg" Condition="AI_INSTALL" Ordering="201"/>
<ROW Dialog_="FolderDlg" Control_="Back" Event="NewDialog" Argument="InstallTypeDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="MaintenanceWelcomeDlg" Control_="Next" Event="NewDialog" Argument="MaintenanceTypeDlg" Condition="AI_MAINT" Ordering="99"/>
<ROW Dialog_="VerifyReadyDlg" Control_="Install" Event="EndDialog" Argument="Return" Condition="AI_MAINT" Ordering="198"/>
<ROW Dialog_="VerifyReadyDlg" Control_="Back" Event="NewDialog" Argument="CustomizeDlg" Condition="AI_MAINT" Ordering="202"/>
<ROW Dialog_="VerifyReadyDlg" Control_="Install" Event="EndDialog" Argument="Return" Condition="AI_INSTALL" Ordering="197"/>
<ROW Dialog_="VerifyReadyDlg" Control_="Back" Event="NewDialog" Argument="FolderDlg" Condition="AI_INSTALL" Ordering="201"/>
<ROW Dialog_="CustomizeDlg" Control_="Next" Event="NewDialog" Argument="VerifyReadyDlg" Condition="AI_MAINT" Ordering="101"/>
<ROW Dialog_="CustomizeDlg" Control_="Back" Event="NewDialog" Argument="MaintenanceTypeDlg" Condition="AI_MAINT" Ordering="1"/>
<ROW Dialog_="MaintenanceTypeDlg" Control_="ChangeButton" Event="NewDialog" Argument="CustomizeDlg" Condition="AI_MAINT" Ordering="501"/>
<ROW Dialog_="MaintenanceTypeDlg" Control_="Back" Event="NewDialog" Argument="MaintenanceWelcomeDlg" Condition="AI_MAINT" Ordering="1"/>
<ROW Dialog_="MaintenanceTypeDlg" Control_="RemoveButton" Event="NewDialog" Argument="VerifyRemoveDlg" Condition="AI_MAINT AND InstallMode=&quot;Remove&quot;" Ordering="601"/>
<ROW Dialog_="VerifyRemoveDlg" Control_="Back" Event="NewDialog" Argument="MaintenanceTypeDlg" Condition="AI_MAINT AND InstallMode=&quot;Remove&quot;" Ordering="1"/>
<ROW Dialog_="MaintenanceTypeDlg" Control_="RepairButton" Event="NewDialog" Argument="VerifyRepairDlg" Condition="AI_MAINT AND InstallMode=&quot;Repair&quot;" Ordering="601"/>
<ROW Dialog_="VerifyRepairDlg" Control_="Back" Event="NewDialog" Argument="MaintenanceTypeDlg" Condition="AI_MAINT AND InstallMode=&quot;Repair&quot;" Ordering="1"/>
<ROW Dialog_="VerifyRepairDlg" Control_="Repair" Event="EndDialog" Argument="Return" Condition="AI_MAINT AND InstallMode=&quot;Repair&quot;" Ordering="399" Options="1"/>
<ROW Dialog_="VerifyRemoveDlg" Control_="Remove" Event="EndDialog" Argument="Return" Condition="AI_MAINT AND InstallMode=&quot;Remove&quot;" Ordering="299" Options="1"/>
<ROW Dialog_="PatchWelcomeDlg" Control_="Next" Event="NewDialog" Argument="VerifyReadyDlg" Condition="AI_PATCH" Ordering="201"/>
<ROW Dialog_="ResumeDlg" Control_="Install" Event="EndDialog" Argument="Return" Condition="AI_RESUME" Ordering="299"/>
<ROW Dialog_="InstallTypeDlg" Control_="Next" Event="NewDialog" Argument="FolderDlg" Condition="AI_INSTALL" Ordering="101"/>
<ROW Dialog_="InstallTypeDlg" Control_="Back" Event="NewDialog" Argument="WelcomeDlg" Condition="AI_INSTALL" Ordering="1"/>
<ROW Dialog_="VerifyReadyDlg" Control_="Install" Event="EndDialog" Argument="Return" Condition="AI_PATCH" Ordering="199"/>
<ROW Dialog_="VerifyReadyDlg" Control_="Back" Event="NewDialog" Argument="PatchWelcomeDlg" Condition="AI_PATCH" Ordering="203"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiCreateFolderComponent">
<ROW Directory_="APPDIR" Component_="APPDIR" ManualDelete="true"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiCustActComponent">
<ROW Action="AI_ApplyShortcutFlags" Type="3073" Source="ShortcutFlags.dll" Target="UpdateShortcutFlags" WithoutSeq="true"/>
<ROW Action="AI_AuthorSinglePackage" Type="1" Source="aicustact.dll" Target="AI_AuthorSinglePackage" WithoutSeq="true"/>
<ROW Action="AI_DOWNGRADE" Type="19" Target="4010"/>
<ROW Action="AI_DetectSoftware" Type="257" Source="SoftwareDetector.dll" Target="OnDetectSoftware"/>
<ROW Action="AI_DpiContentScale" Type="1" Source="aicustact.dll" Target="DpiContentScale"/>
<ROW Action="AI_EnableDebugLog" Type="321" Source="aicustact.dll" Target="EnableDebugLog"/>
<ROW Action="AI_InstallModeCheck" Type="1" Source="aicustact.dll" Target="UpdateInstallMode" WithoutSeq="true"/>
<ROW Action="AI_LaunchApp" Type="1" Source="aicustact.dll" Target="LaunchApp"/>
<ROW Action="AI_PREPARE_UPGRADE" Type="65" Source="aicustact.dll" Target="PrepareUpgrade"/>
<ROW Action="AI_PRESERVE_INSTALL_TYPE" Type="65" Source="aicustact.dll" Target="PreserveInstallType"/>
<ROW Action="AI_PinShortcuts" Type="1" Source="ShortcutFlags.dll" Target="PinShortcuts"/>
<ROW Action="AI_PinToStartScreen" Type="1025" Source="ShortcutFlags.dll" Target="PinToStartScreen" WithoutSeq="true"/>
<ROW Action="AI_PinToTaskbar" Type="1025" Source="ShortcutFlags.dll" Target="PinToTaskbar" WithoutSeq="true"/>
<ROW Action="AI_PrepareShortcutFlags" Type="1" Source="ShortcutFlags.dll" Target="PrepareActionData"/>
<ROW Action="AI_RESTORE_LOCATION" Type="65" Source="aicustact.dll" Target="RestoreLocation"/>
<ROW Action="AI_ResolveKnownFolders" Type="1" Source="aicustact.dll" Target="AI_ResolveKnownFolders"/>
<ROW Action="AI_SETMIXINSTLOCATION" Type="1" Source="aicustact.dll" Target="MixedAllUsersInstallLocation"/>
<ROW Action="AI_SHOW_LOG" Type="65" Source="aicustact.dll" Target="LaunchLogFile" WithoutSeq="true"/>
<ROW Action="AI_STORE_LOCATION" Type="51" Source="ARPINSTALLLOCATION" Target="[APPDIR]"/>
<ROW Action="AI_UnpinFromStartScreen" Type="1025" Source="ShortcutFlags.dll" Target="UnpinFromStartScreen" WithoutSeq="true"/>
<ROW Action="AI_UnpinFromTaskbar" Type="1025" Source="ShortcutFlags.dll" Target="UnpinFromTaskbar" WithoutSeq="true"/>
<ROW Action="AI_UnpinShortcuts" Type="1" Source="ShortcutFlags.dll" Target="UnpinShortcuts"/>
<ROW Action="SET_APPDIR" Type="307" Source="APPDIR" Target="[ProgramFilesFolder][Manufacturer]\[ProductName]" MultiBuildTarget="DefaultBuild:[AI_UserProgramFiles][Manufacturer]\[ProductName]"/>
<ROW Action="SET_SHORTCUTDIR" Type="307" Source="SHORTCUTDIR" Target="[ProgramMenuFolder][ProductName]"/>
<ROW Action="SET_TARGETDIR_TO_APPDIR" Type="51" Source="TARGETDIR" Target="[APPDIR]"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiFeatCompsComponent">
<ROW Feature_="MainFeature" Component_="APPDIR"/>
<ROW Feature_="MainFeature" Component_="ProductInformation"/>
<ROW Feature_="MainFeature" Component_="AI_INSTALLPERUSER"/>
<ROW Feature_="MainFeature" Component_="com0com.cat"/>
<ROW Feature_="MainFeature" Component_="setup.dll"/>
<ROW Feature_="MainFeature" Component_="setupc.exe"/>
<ROW Feature_="MainFeature" Component_="setupg.exe"/>
<ROW Feature_="MainFeature" Component_="uninstall.exe"/>
<ROW Feature_="MainFeature" Component_="Microsoft.Win32.SystemEvents.dll"/>
<ROW Feature_="MainFeature" Component_="System.IO.Ports.dll_1"/>
<ROW Feature_="MainFeature" Component_="Microsoft.Win32.SystemEvents.dll_1"/>
<ROW Feature_="MainFeature" Component_="WpfMaiTouchEmulator.resources.dll"/>
<ROW Feature_="MainFeature" Component_="Microsoft.Windows.SDK.NET.dll"/>
<ROW Feature_="MainFeature" Component_="WpfMaiTouchEmulator.resources.dll_1"/>
<ROW Feature_="MainFeature" Component_="System.Drawing.Common.dll"/>
<ROW Feature_="MainFeature" Component_="System.IO.Ports.dll"/>
<ROW Feature_="MainFeature" Component_="System.Private.Windows.Core.dll"/>
<ROW Feature_="MainFeature" Component_="WinRT.Runtime.dll"/>
<ROW Feature_="MainFeature" Component_="WpfMaiTouchEmulator.deps.json"/>
<ROW Feature_="MainFeature" Component_="WpfMaiTouchEmulator.dll"/>
<ROW Feature_="MainFeature" Component_="WpfMaiTouchEmulator.exe"/>
<ROW Feature_="MainFeature" Component_="Wpf.Ui.dll"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiIconsComponent">
<ROW Name="icon.exe" SourcePath="..\Assets\icon.ico" Index="0"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiInstExSeqComponent">
<ROW Action="AI_DOWNGRADE" Condition="AI_NEWERPRODUCTFOUND AND (UILevel &lt;&gt; 5)" Sequence="210"/>
<ROW Action="AI_RESTORE_LOCATION" Condition="APPDIR=&quot;&quot;" Sequence="749"/>
<ROW Action="AI_STORE_LOCATION" Condition="(Not Installed) OR REINSTALL" Sequence="1501"/>
<ROW Action="AI_PREPARE_UPGRADE" Condition="AI_UPGRADE=&quot;No&quot; AND (Not Installed)" Sequence="1399"/>
<ROW Action="AI_ResolveKnownFolders" Sequence="52"/>
<ROW Action="AI_EnableDebugLog" Sequence="51"/>
<ROW Action="AI_DetectSoftware" Sequence="101"/>
<ROW Action="AI_SETMIXINSTLOCATION" Sequence="748"/>
<ROW Action="AI_PrepareShortcutFlags" Condition="(VersionNT &gt; 501) AND ((NOT Installed) OR (Installed AND (REMOVE&lt;&gt;&quot;ALL&quot;) AND (AI_INSTALL_MODE&lt;&gt;&quot;Remove&quot;)))" Sequence="4501"/>
<ROW Action="AI_PinShortcuts" Condition="(VersionNT &gt; 600) AND ((NOT Installed) OR (Installed AND (REMOVE&lt;&gt;&quot;ALL&quot;) AND (AI_INSTALL_MODE&lt;&gt;&quot;Remove&quot;)))" Sequence="6499"/>
<ROW Action="AI_UnpinShortcuts" Condition="(VersionNT &gt; 600) AND (REMOVE = &quot;ALL&quot;)" Sequence="3199"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiInstallUISequenceComponent">
<ROW Action="AI_PRESERVE_INSTALL_TYPE" Sequence="199"/>
<ROW Action="AI_RESTORE_LOCATION" Condition="APPDIR=&quot;&quot;" Sequence="749"/>
<ROW Action="AI_ResolveKnownFolders" Sequence="53"/>
<ROW Action="AI_DpiContentScale" Sequence="52"/>
<ROW Action="AI_EnableDebugLog" Sequence="51"/>
<ROW Action="AI_DetectSoftware" Sequence="101"/>
<ROW Action="AI_SETMIXINSTLOCATION" Sequence="748"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiLaunchConditionsComponent">
<ROW Condition="( Version9X OR ( NOT VersionNT64 ) OR ( VersionNT64 AND ((VersionNT64 &lt;&gt; 601) OR (MsiNTProductType &lt;&gt; 1)) AND ((VersionNT64 &lt;&gt; 601) OR (MsiNTProductType = 1)) AND ((VersionNT64 &lt;&gt; 602) OR (MsiNTProductType &lt;&gt; 1)) AND ((VersionNT64 &lt;&gt; 602) OR (MsiNTProductType = 1)) AND ((VersionNT64 &lt;&gt; 603) OR (MsiNTProductType &lt;&gt; 1)) AND ((VersionNT64 &lt;&gt; 603) OR (MsiNTProductType = 1)) AND ((VersionNT64 &lt;&gt; 1000) OR (MsiNTProductType = 1)) ) )" Description="[ProductName] cannot be installed on the following Windows versions: [WindowsTypeNT64Display]." DescriptionLocId="AI.LaunchCondition.NoSpecificNT64" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="( Version9X OR VersionNT64 OR ( VersionNT AND (VersionNT &lt;&gt; 601) AND (VersionNT &lt;&gt; 602) AND (VersionNT &lt;&gt; 603) ) )" Description="[ProductName] cannot be installed on the following Windows versions: [WindowsTypeNTDisplay]." DescriptionLocId="AI.LaunchCondition.NoSpecificNT" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="((VersionNT &lt;&gt; 501) AND (VersionNT &lt;&gt; 502))" Description="[ProductName] cannot be installed on [WindowsTypeNT5XDisplay]." DescriptionLocId="AI.LaunchCondition.NoNT5X" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="(VersionNT &lt;&gt; 400)" Description="[ProductName] cannot be installed on [WindowsTypeNT40Display]." DescriptionLocId="AI.LaunchCondition.NoNT40" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="(VersionNT &lt;&gt; 500)" Description="[ProductName] cannot be installed on [WindowsTypeNT50Display]." DescriptionLocId="AI.LaunchCondition.NoNT50" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="(VersionNT &lt;&gt; 600)" Description="[ProductName] cannot be installed on [WindowsTypeNT60Display]." DescriptionLocId="AI.LaunchCondition.NoNT60" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="AI_DETECTED_DOTNET_CORE_VERSION &gt;= AI_REQUIRED_DOTNET_CORE_VERSION" Description="[ProductName] cannot be installed on systems with .NET Core version lower than [AI_REQUIRED_DOTNET_CORE_DISPLAY]." DescriptionLocId="AI.LaunchCondition.DotNETCore" IsPredefined="true" Builds="DefaultBuild"/>
<ROW Condition="VersionNT" Description="[ProductName] cannot be installed on [WindowsType9XDisplay]." DescriptionLocId="AI.LaunchCondition.No9X" IsPredefined="true" Builds="DefaultBuild"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiRegLocatorComponent">
<ROW Signature_="AI_INSTALLPERUSER" Root="2" Key="Software\[Manufacturer]\[UpgradeCode]" Name="AI_INSTALLPERUSER" Type="2"/>
<ROW Signature_="AI_INSTALLPERUSER_1" Root="1" Key="Software\[Manufacturer]\[UpgradeCode]" Name="AI_INSTALLPERUSER" Type="2"/>
<ROW Signature_="AI_INSTALLPERUSER_2" Root="2" Key="Software\[Manufacturer]\[UpgradeCode]" Name="AI_INSTALLPERUSER" Type="18"/>
<ROW Signature_="AI_INSTALLPERUSER_3" Root="1" Key="Software\[Manufacturer]\[UpgradeCode]" Name="AI_INSTALLPERUSER" Type="18"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiRegsComponent">
<ROW Registry="AI_INSTALLPERUSER" Root="-1" Key="Software\[Manufacturer]\[UpgradeCode]" Name="AI_INSTALLPERUSER" Value="[AI_INSTALLPERUSER]" Component_="AI_INSTALLPERUSER"/>
<ROW Registry="Manufacturer" Root="-1" Key="Software\[Manufacturer]" Name="\"/>
<ROW Registry="Path" Root="-1" Key="Software\[Manufacturer]\[ProductName]" Name="Path" Value="[APPDIR]" Component_="ProductInformation"/>
<ROW Registry="ProductName" Root="-1" Key="Software\[Manufacturer]\[ProductName]" Name="\"/>
<ROW Registry="Software" Root="-1" Key="Software" Name="\"/>
<ROW Registry="UpgradeCode" Root="-1" Key="Software\[Manufacturer]\[UpgradeCode]" Name="\"/>
<ROW Registry="Version" Root="-1" Key="Software\[Manufacturer]\[ProductName]" Name="Version" Value="[ProductVersion]" Component_="ProductInformation"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiShortsComponent">
<ROW Shortcut="WpfMaiTouchEmulator" Directory_="ProgramMenuFolder" Name="MAITOU~1|Mai Touch Emulator" Component_="WpfMaiTouchEmulator.exe" Target="[#WpfMaiTouchEmulator.exe]" Hotkey="0" Icon_="icon.exe" IconIndex="0" ShowCmd="1" WkDir="APPDIR" CustomFlags="2"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiThemeComponent">
<ATTRIBUTE name="UsedTheme" value="classic"/>
</COMPONENT>
<COMPONENT cid="caphyon.advinst.msicomp.MsiUpgradeComponent">
<ROW UpgradeCode="[|UpgradeCode]" VersionMin="0.0.1" VersionMax="[|ProductVersion]" Attributes="257" ActionProperty="OLDPRODUCTS"/>
<ROW UpgradeCode="[|UpgradeCode]" VersionMin="[|ProductVersion]" Attributes="2" ActionProperty="AI_NEWERPRODUCTFOUND"/>
</COMPONENT>
</DOCUMENT>

View File

@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
using System.IO;
using System.Diagnostics;
class DumpCreator
public class DumpCreator
{
[Flags]
public enum Typ : uint

View File

@ -7,12 +7,22 @@ public static class Logger
private static readonly object lockObj = new();
private static string? logFilePath;
public static string GetLogPath()
{
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "WpfMaiTouchEmulator");
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
return path;
}
private static string GetLogFilePath()
{
if (logFilePath == null)
{
var fileName = $"app_{DateTime.Now:yyyy-MM-dd}.log";
logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
logFilePath = Path.Combine(GetLogPath(), fileName);
}
return logFilePath;
}
@ -21,7 +31,7 @@ public static class Logger
public static void CleanupOldLogFiles()
{
var directory = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);
var oldFiles = directory.GetFiles("app_*.log")
var oldFiles = new DirectoryInfo(GetLogPath()).GetFiles("app_*.log")
.Where(f => f.CreationTime < DateTime.Now.AddDays(-7))
.ToList();

View File

@ -3,39 +3,140 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
mc:Ignorable="d"
Closing="MainWindow_Closing"
Title="MainWindow" Height="282" Width="800">
<Grid Background="Transparent">
<Button x:Name="ConnectToPortButton" Content="Connect to port" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Click="ConnectToPortButton_Click" Width="105"/>
<TextBox x:Name="RecievedLogBox"
IsReadOnly="True"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
AcceptsReturn="True"
AcceptsTab="True"
TextWrapping="Wrap" Margin="376,34,278,9" />
<TextBox x:Name="SentLogBox"
IsReadOnly="True"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
AcceptsReturn="True"
AcceptsTab="True"
TextWrapping="Wrap" Margin="552,34,102,9" />
<Label x:Name="label" Content="Recieved" HorizontalAlignment="Left" Margin="376,8,0,0" VerticalAlignment="Top" Width="88"/>
<Label x:Name="label_Copy" Content="Sent" HorizontalAlignment="Left" Margin="552,8,0,0" VerticalAlignment="Top" Width="88"/>
<Label x:Name="label_Copy1" Content="Button State" HorizontalAlignment="Left" Margin="698,8,0,0" VerticalAlignment="Top" Width="88"/>
<Label x:Name="buttonStateValue" Content="0" HorizontalAlignment="Left" Margin="698,34,0,0" VerticalAlignment="Top" RenderTransformOrigin="3.566,1.468" FontSize="10"/>
<CheckBox x:Name="debugMode" Content="Debug mode" HorizontalAlignment="Left" Margin="10,39,0,0" VerticalAlignment="Top" IsChecked="{Binding Path=IsDebugEnabled}" Click="debugMode_Click" Cursor="" Focusable="False" ToolTip="Show the touchpad with translucency and show inputs highlighted"/>
<CheckBox x:Name="automaticTouchPanelPositioning" HorizontalAlignment="Left" Margin="10,59,0,0" VerticalAlignment="Top" Height="17" Width="224" Content="Automatic sensor window positioning" IsChecked="{Binding Path=IsAutomaticPositioningEnabled}" Click="automaticTouchPanelPositioning_Click" ToolTip="Try and automatically move the touch sensor window into the correct position on top of Sinma.exe"/>
<CheckBox x:Name="automaticPortConnecting" HorizontalAlignment="Left" Margin="10,81,0,0" VerticalAlignment="Top" Height="17" Width="224" Content="Automatic port connecting" IsChecked="{Binding Path=IsAutomaticPortConnectingEnabled}" Click="automaticPortConnecting_Click" ToolTip="Try to automatically connect to COM23 to send touchsensor updates on app start"/>
<Label x:Name="label1" Content="Connection state" HorizontalAlignment="Left" Margin="238,8,0,0" VerticalAlignment="Top"/>
<Label x:Name="connectionStateLabel" Content="Not connected" HorizontalAlignment="Left" Margin="238,31,0,0" VerticalAlignment="Top" FontSize="10"/>
<CheckBox x:Name="exitWithSinmai" HorizontalAlignment="Left" Margin="10,103,0,0" VerticalAlignment="Top" Height="17" Width="224" Content="Exit when Sinmai exits" IsChecked="{Binding IsExitWithSinmaiEnabled}" Click="exitWithSinmai_Click" ToolTip="Try and detect when Sinmai.exe exits and exit this app alongside it"/>
<Button x:Name="buttonInstallComPort" Content="Install com port" HorizontalAlignment="Left" Margin="10,147,0,0" VerticalAlignment="Top" Click="buttonInstallComPort_Click"/>
<Button x:Name="buttonUninstallComPorts" Content="Uninstall com port" HorizontalAlignment="Left" Margin="10,172,0,0" VerticalAlignment="Top" Click="buttonUninstallComPorts_Click"/>
<Button x:Name="buttonListComPorts" Content="List installed com ports" HorizontalAlignment="Left" Margin="10,197,0,0" VerticalAlignment="Top" Click="buttonListComPorts_Click"/>
Background="AliceBlue"
Title="MainWindow" Height="700" Width="500" WindowStartupLocation="CenterScreen">
<ScrollViewer>
<StackPanel MaxWidth="400" Margin="0,0,0,50">
<Menu>
<MenuItem Header="{Binding LbMenuCategoryHelp}" d:Header="_Help">
<MenuItem.Icon>
<ui:SymbolIcon Symbol="BookQuestionMark20" />
</MenuItem.Icon>
<MenuItem x:Name="instructionsLabel" Header="{Binding LbMenuItemSetup}" Click="instructionsLabel_Click"/>
</MenuItem>
</Menu>
<ui:Card Margin="0,0,0,10">
<StackPanel>
<ui:TextBlock d:Text="Connection State" Text="{Binding LbConnectionState}"/>
<Label x:Name="connectionStateLabel" Content="{Binding LbConnectionStateNotConnected}" d:Content="Not connected" FontSize="10"/>
<Separator Margin="0,6"/>
<Label x:Name="recievedDataLabel" Content="{Binding LbRecievedData}" d:Content="Recieved Data"/>
<ui:TextBox x:Name="RecievedLogBox"
IsReadOnly="True"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
Focusable="False"
AcceptsReturn="True"
AcceptsTab="True"
TextWrapping="Wrap" Height="80">
<ui:TextBox.Icon>
<ui:SymbolIcon Symbol="ArrowDownLeft20" />
</ui:TextBox.Icon>
</ui:TextBox>
<Label x:Name="sentDataLabel" Content="{Binding LbSentData}" d:Content="Sent Data"/>
<ui:TextBox x:Name="SentLogBox"
IsReadOnly="True"
Focusable="False"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
AcceptsReturn="True"
AcceptsTab="True"
TextWrapping="Wrap" Height="80">
<ui:TextBox.Icon>
<ui:SymbolIcon Symbol="ArrowUpRight20" />
</ui:TextBox.Icon>
</ui:TextBox>
<Separator Margin="0,6"/>
<Label x:Name="buttonStateLabel" Content="{Binding LbButtonState}" d:Content="Button state" />
<Label x:Name="buttonStateValue" Content="0" />
</StackPanel>
</ui:Card>
<ui:CardExpander
CornerRadius="8"
Margin="0,0,0,10"
Header="{Binding LbComPort}"
d:Header="COM Port">
<ui:CardExpander.Icon>
<ui:SymbolIcon Symbol="PlugDisconnected20" />
</ui:CardExpander.Icon>
<StackPanel>
<ui:Button x:Name="ConnectToPortButton" Content="{Binding LbConnectToPort}" d:Content="Connect to port" Click="ConnectToPortButton_Click" />
<Separator Margin="0,6"/>
<ui:Button x:Name="buttonInstallComPort" Content="{Binding LbInstallComPort}" Margin="0,0,0,10" d:Content="Install COM port" Click="buttonInstallComPort_Click" />
<ui:Button x:Name="buttonUninstallComPorts" Content="{Binding LbUninstallComPort}" Margin="0,0,0,10" d:Content="Uninstall COM port" Click="buttonUninstallComPorts_Click" />
<ui:Button x:Name="buttonListComPorts" Content="{Binding LbListComPorts}" d:Content="List installed COM ports" Click="buttonListComPorts_Click" />
</StackPanel>
</ui:CardExpander>
<ui:CardExpander
CornerRadius="8"
Margin="0,0,0,10"
Header="{Binding LbBorderSettings}"
d:Header="Border settings">
<ui:CardExpander.Icon>
<ui:SymbolIcon Symbol="EditSettings20" />
</ui:CardExpander.Icon>
<StackPanel>
<RadioButton x:Name="rbBorderDisabled" Content="{Binding LbBorderDisabled}" d:Content="Disabled" GroupName="ColorOptions" Checked="RadioButton1_Checked"/>
<RadioButton x:Name="rbBorderSolidColour" Content="{Binding LbBorderSolid}" d:Content="Solid Colour" GroupName="ColorOptions" Checked="RadioButton2_Checked"/>
<ui:TextBox x:Name="txtBorderHexColor" Text="{Binding Path=BorderColour}" PlaceholderText="#AARRGGBB" IsEnabled="False" Margin="10,10,0,10" TextChanged="txtBorderHexColor_TextChanged"/>
<RadioButton Name="rbBorderRainbow" Content="{Binding LbBorderRainbow}" d:Content="Rainbow" GroupName="ColorOptions" Checked="RadioButton3_Checked"/>
</StackPanel>
</ui:CardExpander>
<ui:CardExpander
CornerRadius="8"
Margin="0,0,0,10"
Header="{Binding LbSettings}"
d:Header="Settings">
<ui:CardExpander.Icon>
<ui:SymbolIcon Symbol="Settings20" />
</ui:CardExpander.Icon>
<StackPanel>
<Label x:Name="languageLabel" Content="{Binding LbLanguageDropdown}" d:Content="Language"/>
<ComboBox x:Name="languageSelector" ItemsSource="{Binding SupportedLanguages}" DisplayMemberPath="NativeName" SelectedItem="{Binding SelectedLanguage, Mode=TwoWay}"/>
<Separator Margin="0,6"/>
<CheckBox x:Name="largeButtons" Content="{Binding LbLargeButtons}" d:Content="LargeButtons" IsChecked="{Binding Path=IsLargeButtonsEnabled}" Click="largeButtons_Click" Cursor="" Focusable="False" ToolTip="{Binding LbLargeButtonsTT}" />
<CheckBox x:Name="debugMode" Content="{Binding LbDebugMode}" d:Content="Debug mode" IsChecked="{Binding Path=IsDebugEnabled}" Click="debugMode_Click" Cursor="" Focusable="False" ToolTip="{Binding LbDebugModeTT}" />
<CheckBox x:Name="automaticTouchPanelPositioning" d:Content="Automatic touch panel positioning" Content="{Binding LbAutoSensorPositioning}" IsChecked="{Binding Path=IsAutomaticPositioningEnabled}" Click="automaticTouchPanelPositioning_Click" ToolTip="{Binding LbAutoSensorPositioningTT}"/>
<CheckBox x:Name="automaticPortConnecting" d:Content="Automatic port connecting" Content="{Binding LbAutoPortConnecting}" IsChecked="{Binding Path=IsAutomaticPortConnectingEnabled}" Click="automaticPortConnecting_Click" ToolTip="{Binding LbAutoPortConnectingTT}"/>
<CheckBox x:Name="exitWithSinmai" Content="{Binding LbExitWithSinmai}" d:Content="Exit with Sinmai" IsChecked="{Binding IsExitWithSinmaiEnabled}" Click="exitWithSinmai_Click" ToolTip="{Binding LbExitWithSinmaiTT}"/>
<CheckBox x:Name="emulateRingButtons" Content="{Binding LbEmulateRingButtons}" d:Content="Emulate ring buttons" IsChecked="{Binding IsRingButtonEmulationEnabled}" ToolTip="{Binding LbEmulateRingButtonsTT}" Click="emulateRingButtons_Click"/>
<ui:Button x:Name="openLogFolderButton" Content="{Binding LbOpenLogFolder}" d:Content="Open log folder" Click="openLogFolderButton_Click" />
</StackPanel>
</ui:CardExpander>
<ui:Card>
<StackPanel>
<TextBlock Text="{Binding LbAbout}" d:Text="About" Margin="0,0,0,10"/>
<ui:Anchor
Margin="0"
Padding="16"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Background="Transparent"
BorderThickness="0,1,0,0"
CornerRadius="0"
NavigateUri="https://github.com/Leapward-Koex/MaiTouchSensorEmulator">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Github" />
<ui:SymbolIcon Grid.Column="1" Symbol="WindowNew20" />
</Grid>
</ui:Anchor>
<Label Content="{Binding LbAppVersion}" d:Content="1.0.0.0"/>
<Label Content="By Leapward-Koex"/>
</StackPanel>
</ui:Card>
</StackPanel>
</ScrollViewer>
</Grid>
</Window>

View File

@ -1,22 +1,46 @@
using System.Diagnostics;
using System.Reflection;
using System.Windows;
using WpfMaiTouchEmulator.Managers;
namespace WpfMaiTouchEmulator;
public enum BorderSetting
{
Disabled,
Solid,
Rainbow
}
public partial class MainWindow : Window
{
private readonly MaiTouchSensorButtonStateManager buttonState;
private MaiTouchComConnector connector;
private readonly MaiTouchComConnector connector;
private readonly VirtualComPortManager comPortManager;
private TouchPanel _touchPanel;
private TouchPanel? _touchPanel;
public MainWindow()
{
InitializeComponent();
CheckForUserSettingsUpgrade();
DataContext = new MainWindowViewModel()
{
IsDebugEnabled = Properties.Settings.Default.IsDebugEnabled,
IsAutomaticPortConnectingEnabled = Properties.Settings.Default.IsAutomaticPortConnectingEnabled,
IsAutomaticPositioningEnabled = Properties.Settings.Default.IsAutomaticPositioningEnabled,
IsExitWithSinmaiEnabled = Properties.Settings.Default.IsExitWithSinmaiEnabled,
IsRingButtonEmulationEnabled = Properties.Settings.Default.IsRingButtonEmulationEnabled,
IsLargeButtonsEnabled = Properties.Settings.Default.IsLargeButtonsEnabled,
BorderColour = Properties.Settings.Default.BorderColour,
LbAppVersion = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "1.0.0.0",
};
LoadBorderRadioButtonSetting();
Title = "Mai Touch Emulator";
buttonState = new MaiTouchSensorButtonStateManager(buttonStateValue);
connector = new MaiTouchComConnector(buttonState);
comPortManager = new VirtualComPortManager();
connector = new MaiTouchComConnector(buttonState, (MainWindowViewModel)DataContext);
comPortManager = new VirtualComPortManager((MainWindowViewModel)DataContext);
connector.OnConnectStatusChange = (status) =>
{
connectionStateLabel.Content = status;
@ -48,28 +72,33 @@ public partial class MainWindow : Window
if (Properties.Settings.Default.FirstOpen)
{
Logger.Info("First open occurred");
MessageBox.Show("Please remove any COM devices using the COM3 port before installing the virtual COM port. In Device Manager click \"View\" then enabled \"Show hidden devices\" and uninstall any devices that are using the COM3 port.\n\nAfter ensuring COM3 is free please use the install COM port button in the app to register the app.\n\nThe app needs to connect to the port prior to Sinmai.exe being opened.", "First time setup", MessageBoxButton.OK, MessageBoxImage.Information);
ShowSetupInstructionsDialog();
Properties.Settings.Default.FirstOpen = false;
Properties.Settings.Default.Save();
}
Loaded += (s, e) => {
var dataContext = (MainWindowViewModel)DataContext;
Logger.Info("Main window loaded, creating touch panel");
_touchPanel = new TouchPanel();
_touchPanel.onTouch = (value) => { buttonState.PressButton(value); };
_touchPanel.onRelease = (value) => { buttonState.ReleaseButton(value); };
_touchPanel.Show();
_touchPanel.Owner = this;
DataContext = new MainWindowViewModel()
{
IsDebugEnabled = Properties.Settings.Default.IsDebugEnabled,
IsAutomaticPortConnectingEnabled = Properties.Settings.Default.IsAutomaticPortConnectingEnabled,
IsAutomaticPositioningEnabled = Properties.Settings.Default.IsAutomaticPositioningEnabled,
IsExitWithSinmaiEnabled = Properties.Settings.Default.IsExitWithSinmaiEnabled
_touchPanel.onTouch = (value) => {
buttonState.PressButton(value);
connector.SendTouchscreenState();
};
_touchPanel.onRelease = (value) => {
buttonState.ReleaseButton(value);
connector.SendTouchscreenState();
};
_touchPanel.onInitialReposition = () => { WindowState = WindowState.Minimized; };
_touchPanel.SetBorderMode((BorderSetting)Properties.Settings.Default.BorderSetting, dataContext.BorderColour);
_touchPanel.Show();
_touchPanel.DataContext = dataContext;
var dataContext = (MainWindowViewModel)DataContext;
_touchPanel.SetDebugMode(dataContext.IsDebugEnabled);
_touchPanel.SetLargeButtonMode(dataContext.IsLargeButtonsEnabled);
if (Properties.Settings.Default.IsAutomaticPositioningEnabled)
{
_touchPanel.DragWindowHandle.Visibility = Visibility.Hidden;
@ -81,16 +110,14 @@ public partial class MainWindow : Window
};
}
private async void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
private async void MainWindow_Closing(object? sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
await connector.Disconnect();
foreach (Window childWindow in OwnedWindows)
{
childWindow.Close();
}
_touchPanel?.Close();
Closing -= MainWindow_Closing;
Close();
e.Cancel = false;
Application.Current.Shutdown();
}
private async void ExitWithSinmaiLoop()
@ -131,11 +158,29 @@ public partial class MainWindow : Window
dataContext.IsExitWithSinmaiEnabled = false;
Properties.Settings.Default.IsExitWithSinmaiEnabled = dataContext.IsExitWithSinmaiEnabled;
Properties.Settings.Default.Save();
MessageBox.Show("Failed to listen for Sinmai exit signal, is it running as admin?\n\nAutomatic exiting disabled.", "Failed to listen for Sinmai exit", MessageBoxButton.OK, MessageBoxImage.Information);
MessageBox.Show(dataContext.TxtFailedToSetupSinmaiExit, dataContext.TxtFailedToSetupSinmaiExitHeader, MessageBoxButton.OK, MessageBoxImage.Information);
}
}
}
private static void CheckForUserSettingsUpgrade()
{
if (!Properties.Settings.Default.UserSettingsUpgradeRequired)
{
return;
}
Properties.Settings.Default.Upgrade();
Properties.Settings.Default.UserSettingsUpgradeRequired = false;
Properties.Settings.Default.Save();
}
private void ShowSetupInstructionsDialog()
{
var dataContext = (MainWindowViewModel)DataContext;
MessageBox.Show(dataContext.TxtSetupInstructions, dataContext.TxtSetupInstructionsHeader, MessageBoxButton.OK, MessageBoxImage.Information);
}
private async void AutomaticTouchPanelPositioningLoop()
{
var dataContext = (MainWindowViewModel)DataContext;
@ -143,7 +188,7 @@ public partial class MainWindow : Window
{
if (dataContext.IsAutomaticPositioningEnabled)
{
_touchPanel.PositionTouchPanel();
_touchPanel?.PositionTouchPanel();
}
await Task.Delay(1000);
@ -157,16 +202,16 @@ public partial class MainWindow : Window
{
if (dataContext.IsAutomaticPortConnectingEnabled)
{
await connector.StartTouchSensorPolling();
connector.StartTouchSensorPolling();
}
await Task.Delay(1000);
}
}
private async void ConnectToPortButton_Click(object sender, RoutedEventArgs e)
private void ConnectToPortButton_Click(object sender, RoutedEventArgs e)
{
await connector.StartTouchSensorPolling();
connector.StartTouchSensorPolling();
}
private void debugMode_Click(object sender, RoutedEventArgs e)
@ -176,7 +221,17 @@ public partial class MainWindow : Window
dataContext.IsDebugEnabled = !enabled;
Properties.Settings.Default.IsDebugEnabled = dataContext.IsDebugEnabled;
Properties.Settings.Default.Save();
_touchPanel.SetDebugMode(dataContext.IsDebugEnabled);
_touchPanel?.SetDebugMode(dataContext.IsDebugEnabled);
}
private void largeButtons_Click(object sender, RoutedEventArgs e)
{
var dataContext = (MainWindowViewModel)DataContext;
var enabled = !dataContext.IsLargeButtonsEnabled;
dataContext.IsLargeButtonsEnabled = !enabled;
Properties.Settings.Default.IsLargeButtonsEnabled = dataContext.IsLargeButtonsEnabled;
Properties.Settings.Default.Save();
_touchPanel?.SetLargeButtonMode(dataContext.IsLargeButtonsEnabled);
}
private void automaticTouchPanelPositioning_Click(object sender, RoutedEventArgs e)
@ -223,6 +278,76 @@ public partial class MainWindow : Window
private void buttonListComPorts_Click(object sender, RoutedEventArgs e)
{
var output = comPortManager.GetInstalledPorts();
MessageBox.Show(string.Join("\n", output), "Installed ports");
var dataContext = (MainWindowViewModel)DataContext;
MessageBox.Show(string.Join("\n", output), dataContext.TxtCurrentlyInstalledPorts);
}
private void instructionsLabel_Click(object sender, RoutedEventArgs e)
{
ShowSetupInstructionsDialog();
}
private void emulateRingButtons_Click(object sender, RoutedEventArgs e)
{
var dataContext = (MainWindowViewModel)DataContext;
var enabled = !dataContext.IsRingButtonEmulationEnabled;
dataContext.IsRingButtonEmulationEnabled = !enabled;
Properties.Settings.Default.IsRingButtonEmulationEnabled = dataContext.IsRingButtonEmulationEnabled;
Properties.Settings.Default.Save();
_touchPanel?.SetEmulateRingButton(dataContext.IsRingButtonEmulationEnabled);
}
private void LoadBorderRadioButtonSetting()
{
rbBorderDisabled.IsChecked = Properties.Settings.Default.BorderSetting == (int)BorderSetting.Disabled;
txtBorderHexColor.IsEnabled = Properties.Settings.Default.BorderSetting == (int)BorderSetting.Solid;
rbBorderSolidColour.IsChecked = Properties.Settings.Default.BorderSetting == (int)BorderSetting.Solid;
rbBorderRainbow.IsChecked = Properties.Settings.Default.BorderSetting == (int)BorderSetting.Rainbow;
}
private void RadioButton1_Checked(object sender, RoutedEventArgs e)
{
txtBorderHexColor.IsEnabled = false;
Properties.Settings.Default.BorderSetting = (int)BorderSetting.Disabled;
Properties.Settings.Default.Save();
_touchPanel?.SetBorderMode(BorderSetting.Disabled, "");
}
private void RadioButton2_Checked(object sender, RoutedEventArgs e)
{
txtBorderHexColor.IsEnabled = true;
Properties.Settings.Default.BorderSetting = (int)BorderSetting.Solid;
Properties.Settings.Default.Save();
_touchPanel?.SetBorderMode(BorderSetting.Solid, Properties.Settings.Default.BorderColour);
}
private void RadioButton3_Checked(object sender, RoutedEventArgs e)
{
txtBorderHexColor.IsEnabled = false;
Properties.Settings.Default.BorderSetting = (int)BorderSetting.Rainbow;
Properties.Settings.Default.Save();
_touchPanel?.SetBorderMode(BorderSetting.Rainbow, "");
}
private void txtBorderHexColor_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
var textWithoutHash = txtBorderHexColor.Text.TrimStart('#') ?? "";
if (textWithoutHash.Length == 6 || textWithoutHash.Length == 8)
{
var textWithHash = "#" + textWithoutHash;
Properties.Settings.Default.BorderColour = textWithHash;
Properties.Settings.Default.Save();
_touchPanel?.SetBorderMode(BorderSetting.Solid, textWithHash);
}
}
private void openLogFolderButton_Click(object sender, RoutedEventArgs e)
{
Process.Start("explorer.exe", Logger.GetLogPath());
}
}

View File

@ -1,14 +1,273 @@
using System.ComponentModel;
using System.Globalization;
using System.Resources;
using System.Runtime.CompilerServices;
namespace WpfMaiTouchEmulator;
internal class MainWindowViewModel : INotifyPropertyChanged
public class MainWindowViewModel : INotifyPropertyChanged
{
// Language
public string? LbAutoPortConnecting
{
get; set;
}
public string? LbAutoSensorPositioning
{
get; set;
}
public string? LbButtonState
{
get; set;
}
public string? LbConnectionState
{
get; set;
}
public string? LbConnectionStateNotConnected
{
get; set;
}
public string? LbConnectToPort
{
get; set;
}
public string? LbDebugMode
{
get; set;
}
public string? LbLargeButtons
{
get; set;
}
public string? LbLargeButtonsTT
{
get; set;
}
public string? LbExitWithSinmai
{
get; set;
}
public string? LbEmulateRingButtons
{
get; set;
}
public string? LbOpenLogFolder
{
get; set;
}
public string? LbAppVersion
{
get; set;
}
public string? LbAbout
{
get; set;
}
public string? LbInstallComPort
{
get; set;
}
public string? LbLanguageDropdown
{
get; set;
}
public string? LbBorderSettings
{
get; set;
}
public string? LbSettings
{
get; set;
}
public string? LbComPort
{
get; set;
}
public string? LbBorderDisabled
{
get; set;
}
public string? LbBorderSolid
{
get; set;
}
public string? LbBorderRainbow
{
get; set;
}
public string? LbListComPorts
{
get; set;
}
public string? LbReceivedData
{
get; set;
}
public string? LbRecievedData
{
get;
private set;
}
public string? LbSentData
{
get; set;
}
public string? LbUninstallComPort
{
get; set;
}
public string? LbMenuCategoryHelp
{
get;
private set;
}
public string? LbMenuItemSetup
{
get;
private set;
}
public string? TxtSetupInstructions
{
get;
private set;
}
public string? TxtSetupInstructionsHeader
{
get;
private set;
}
public string? TxtFailedToSetupSinmaiExitHeader
{
get;
private set;
}
public string? TxtFailedToSetupSinmaiExit
{
get;
private set;
}
public string? TxtCurrentlyInstalledPorts
{
get;
private set;
}
public string? TxtErrorConnectingToPortHeader
{
get;
private set;
}
public string? TxtComPortConnected
{
get;
private set;
}
public string? TxtComPortConnecting
{
get;
private set;
}
public string? TxtCom3AlreadyInstalled
{
get;
private set;
}
public string? TxtCom3InstalledSuccessfully
{
get;
private set;
}
public string? TxtCom3InstallFailed
{
get;
private set;
}
public string? TxtCom3UninstallNotRequired
{
get;
private set;
}
public string? TxtCom3UninstalledSuccessfully
{
get;
private set;
}
public string? TxtCom3UninstallFailed
{
get;
private set;
}
public string? LbTouchPanelResize
{
get;
private set;
}
public string? LbTouchPanelDrag
{
get;
private set;
}
public string? LbAutoPortConnectingTT
{
get;
private set;
}
public string? LbDebugModeTT
{
get;
private set;
}
public string? LbAutoSensorPositioningTT
{
get;
private set;
}
public string? LbExitWithSinmaiTT
{
get;
private set;
}
public string? LbEmulateRingButtonsTT
{
get;
private set;
}
private bool _isAutomaticPortConnectingEnabled;
private bool _isDebugEnabled;
private bool _isLargeButtonsEnabled;
private bool _isAutomaticPositioningEnabled;
private bool _isExitWithSinmaiEnabled;
private CultureInfo _selectedLanguage;
private bool _isRingButtonEmulationEnabled;
private string _borderColour;
private readonly ResourceManager resourceManager;
private readonly CultureInfo cultureInfo;
public List<CultureInfo> SupportedLanguages
{
get;
} =
[
new CultureInfo("en-US"), // English
new CultureInfo("zh-CN"), // Chinese (Simplified)
new CultureInfo("ja-JP") // Japanese
];
public MainWindowViewModel()
{
resourceManager = new ResourceManager("WpfMaiTouchEmulator.Properties.Resources", typeof(MainWindowViewModel).Assembly);
LoadLanguageSettings();
}
private void LoadLanguageSettings()
{
var savedLang = Properties.Settings.Default.UserLanguage;
var culture = string.IsNullOrEmpty(savedLang) ? CultureInfo.CurrentCulture : new CultureInfo(savedLang);
SelectedLanguage = SupportedLanguages.Contains(culture) ? culture : SupportedLanguages[0];
}
public bool IsDebugEnabled
{
@ -20,6 +279,16 @@ internal class MainWindowViewModel : INotifyPropertyChanged
}
}
public bool IsLargeButtonsEnabled
{
get => _isLargeButtonsEnabled;
set
{
_isLargeButtonsEnabled = value;
OnPropertyChanged();
}
}
public bool IsAutomaticPositioningEnabled
{
get => _isAutomaticPositioningEnabled;
@ -50,6 +319,114 @@ internal class MainWindowViewModel : INotifyPropertyChanged
}
}
public bool IsRingButtonEmulationEnabled
{
get => _isRingButtonEmulationEnabled;
set
{
_isRingButtonEmulationEnabled = value;
OnPropertyChanged();
}
}
public string BorderColour
{
get => _borderColour;
set
{
_borderColour = value;
OnPropertyChanged();
}
}
public CultureInfo SelectedLanguage
{
get => _selectedLanguage;
set
{
if (_selectedLanguage != value)
{
_selectedLanguage = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedLanguage)));
ChangeLanguage(value);
}
}
}
private void ChangeLanguage(CultureInfo culture)
{
Properties.Settings.Default.UserLanguage = culture.Name;
Properties.Settings.Default.Save();
Thread.CurrentThread.CurrentUICulture = culture;
ResourceManager rm = new ResourceManager(typeof(Resources.Strings));
UpdateLocalizedResources(rm);
}
private void UpdateLocalizedResources(ResourceManager resourceManager)
{
LbAutoPortConnecting = resourceManager.GetString("lbAutoPortConnecting");
LbAutoSensorPositioning = resourceManager.GetString("lbAutoSensorPositioning");
LbButtonState = resourceManager.GetString("lbButtonState");
LbConnectionState = resourceManager.GetString("lbConnectionState");
LbConnectionStateNotConnected = resourceManager.GetString("lbConnectionStateNotConnected");
LbConnectToPort = resourceManager.GetString("lbConnectToPort");
LbDebugMode = resourceManager.GetString("lbDebugMode");
LbLargeButtons = resourceManager.GetString("lbLargeButtons");
LbLargeButtonsTT = resourceManager.GetString("lbLargeButtonsTT");
LbExitWithSinmai = resourceManager.GetString("lbExitWithSinmai");
LbEmulateRingButtons = resourceManager.GetString("lbEmulateRingButtons");
LbOpenLogFolder = resourceManager.GetString("LbOpenLogFolder");
LbAbout = resourceManager.GetString("LbAbout");
LbInstallComPort = resourceManager.GetString("lbInstallComPort");
LbLanguageDropdown = resourceManager.GetString("lbLanguageDropdown");
LbComPort = resourceManager.GetString("LbComPort");
LbBorderSettings = resourceManager.GetString("LbBorderSettings");
LbSettings = resourceManager.GetString("LbSettings");
LbBorderDisabled = resourceManager.GetString("LbBorderDisabled");
LbBorderSolid = resourceManager.GetString("LbBorderSolid");
LbBorderRainbow = resourceManager.GetString("LbBorderRainbow");
LbListComPorts = resourceManager.GetString("lbListComPorts");
LbReceivedData = resourceManager.GetString("lbReceivedData");
LbRecievedData = resourceManager.GetString("lbRecievedData");
LbSentData = resourceManager.GetString("lbSentData");
LbUninstallComPort = resourceManager.GetString("lbUninstallComPort");
LbMenuCategoryHelp = resourceManager.GetString("lbMenuCategoryHelp");
LbMenuItemSetup = resourceManager.GetString("lbMenuItemSetup");
LbAutoPortConnectingTT = resourceManager.GetString("lbAutoPortConnectingTT");
LbDebugModeTT = resourceManager.GetString("lbDebugModeTT");
LbAutoSensorPositioningTT = resourceManager.GetString("lbAutoSensorPositioningTT");
LbExitWithSinmaiTT = resourceManager.GetString("lbExitWithSinmaiTT");
LbEmulateRingButtonsTT = resourceManager.GetString("lbEmulateRingButtonsTT");
LbMenuCategoryHelp = resourceManager.GetString("lbMenuCategoryHelp");
LbMenuItemSetup = resourceManager.GetString("lbMenuItemSetup");
TxtSetupInstructions = resourceManager.GetString("TxtSetupInstructions");
TxtSetupInstructionsHeader = resourceManager.GetString("TxtSetupInstructionsHeader");
TxtFailedToSetupSinmaiExitHeader = resourceManager.GetString("TxtFailedToSetupSinmaiExitHeader");
TxtFailedToSetupSinmaiExit = resourceManager.GetString("TxtFailedToSetupSinmaiExit");
TxtCurrentlyInstalledPorts = resourceManager.GetString("TxtCurrentlyInstalledPorts");
TxtErrorConnectingToPortHeader = resourceManager.GetString("TxtErrorConnectingToPortHeader");
TxtComPortConnected = resourceManager.GetString("TxtComPortConnected");
TxtComPortConnecting = resourceManager.GetString("TxtComPortConnecting");
TxtCom3AlreadyInstalled = resourceManager.GetString("TxtCom3AlreadyInstalled");
TxtCom3InstalledSuccessfully = resourceManager.GetString("TxtCom3InstalledSuccessfully");
TxtCom3InstallFailed = resourceManager.GetString("TxtCom3InstallFailed");
TxtCom3UninstallNotRequired = resourceManager.GetString("TxtCom3UninstallNotRequired");
TxtCom3UninstalledSuccessfully = resourceManager.GetString("TxtCom3UninstalledSuccessfully");
TxtCom3UninstallFailed = resourceManager.GetString("TxtCom3UninstallFailed");
LbTouchPanelResize = resourceManager.GetString("LbTouchPanelResize");
LbTouchPanelDrag = resourceManager.GetString("LbTouchPanelDrag");
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)

View File

@ -1,16 +1,17 @@
using System.IO.Ports;
using System.Windows;
namespace WpfMaiTouchEmulator;
internal class MaiTouchComConnector(MaiTouchSensorButtonStateManager buttonState)
namespace WpfMaiTouchEmulator.Managers;
internal class MaiTouchComConnector(MaiTouchSensorButtonStateManager buttonState, MainWindowViewModel viewModel)
{
private static SerialPort? serialPort;
private bool isActiveMode;
private bool _isActiveMode;
private bool _connected;
private CancellationTokenSource? _tokenSource;
private Thread? _pollThread;
private bool _shouldReconnect = true;
private readonly MaiTouchSensorButtonStateManager _buttonState = buttonState;
private readonly MainWindowViewModel _viewModel = viewModel;
public Action<string>? OnConnectStatusChange
{
@ -33,15 +34,15 @@ internal class MaiTouchComConnector(MaiTouchSensorButtonStateManager buttonState
internal set;
}
public async Task StartTouchSensorPolling()
public void StartTouchSensorPolling()
{
if (!_connected && _shouldReconnect)
{
Logger.Info("Trying to connect to COM port...");
var virtualPort = "COM23"; // Adjust as needed
var virtualPort = "COM23";
try
{
OnConnectStatusChange?.Invoke("Conecting...");
OnConnectStatusChange?.Invoke(_viewModel.TxtComPortConnecting);
serialPort = new SerialPort(virtualPort, 9600, Parity.None, 8, StopBits.One)
{
WriteTimeout = 100
@ -49,7 +50,7 @@ internal class MaiTouchComConnector(MaiTouchSensorButtonStateManager buttonState
serialPort.DataReceived += SerialPort_DataReceived;
serialPort.Open();
Logger.Info("Serial port opened successfully.");
OnConnectStatusChange?.Invoke("Connected to port");
OnConnectStatusChange?.Invoke(_viewModel.TxtComPortConnected);
_connected = true;
_tokenSource = new CancellationTokenSource(); // Create a token source.
@ -64,12 +65,12 @@ internal class MaiTouchComConnector(MaiTouchSensorButtonStateManager buttonState
OnConnectError?.Invoke();
Application.Current.Dispatcher.Invoke(() =>
{
MessageBox.Show(ex.Message, "Error connecting to COM port", MessageBoxButton.OK, MessageBoxImage.Error);
MessageBox.Show(ex.Message, _viewModel.TxtErrorConnectingToPortHeader, MessageBoxButton.OK, MessageBoxImage.Error);
});
Logger.Error("Error on starting polling", ex);
Logger.Info("Disconnecting from COM port");
_connected = false;
OnConnectStatusChange?.Invoke("Not Connected");
OnConnectStatusChange?.Invoke(_viewModel.LbConnectionStateNotConnected);
if (serialPort?.IsOpen == true)
{
serialPort.DiscardInBuffer();
@ -85,10 +86,10 @@ internal class MaiTouchComConnector(MaiTouchSensorButtonStateManager buttonState
{
while (!token.IsCancellationRequested)
{
if (isActiveMode)
if (_isActiveMode)
{
SendTouchscreenState();
Thread.Sleep(1);
Thread.Sleep(10);
}
else
{
@ -138,55 +139,60 @@ internal class MaiTouchComConnector(MaiTouchSensorButtonStateManager buttonState
void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
var recievedData = serialPort?.ReadExisting();
var commands = recievedData?.Split(new[] { '}' }, StringSplitOptions.RemoveEmptyEntries);
if (commands != null)
var commands = recievedData?.Split(['}'], StringSplitOptions.RemoveEmptyEntries);
if (commands is null)
{
foreach (var command in commands)
return;
}
foreach (var command in commands)
{
var cleanedCommand = command.TrimStart('{');
Logger.Info($"Received serial data: '{cleanedCommand}'");
OnDataRecieved?.Invoke(cleanedCommand);
if (cleanedCommand == "STAT")
{
_isActiveMode = true;
}
else if (cleanedCommand == "RSET")
{
var cleanedCommand = command.TrimStart('{');
Logger.Info($"Received serial data: {cleanedCommand}");
OnDataRecieved?.Invoke(cleanedCommand);
if (cleanedCommand == "STAT")
{
isActiveMode = true;
}
else if (cleanedCommand == "RSET")
{
}
else if (cleanedCommand == "HALT")
{
_isActiveMode = false;
}
else if (cleanedCommand.Length >= 4 &&
(cleanedCommand[2] == 'r' || cleanedCommand[2] == 'k'))
{
var leftOrRight = cleanedCommand[0];
var sensor = cleanedCommand[1];
var ratio = cleanedCommand[3];
}
else if (cleanedCommand == "HALT")
{
isActiveMode = false;
}
else if (cleanedCommand[2] == 'r' || cleanedCommand[2] == 'k')
{
var leftOrRight = cleanedCommand[0];
var sensor = cleanedCommand[1];
var ratio = cleanedCommand[3];
var newString = $"({leftOrRight}{sensor}{cleanedCommand[2]}{ratio})";
serialPort?.Write(newString);
OnDataSent?.Invoke(newString);
}
else
{
Logger.Warn($"Unhandled serial data command {cleanedCommand}");
}
var newString = $"({leftOrRight}{sensor}{cleanedCommand[2]}{ratio})";
serialPort?.Write(newString);
OnDataSent?.Invoke(newString);
}
else
{
Logger.Warn($"Unhandled serial data command '{cleanedCommand}'");
}
}
}
void SendTouchscreenState()
public void SendTouchscreenState()
{
if (_connected)
if (_connected && _isActiveMode)
{
var currentState = _buttonState.GetCurrentState();
try
{
serialPort?.Write(currentState, 0, currentState.Length);
}
catch (Exception ex) {
catch (Exception ex)
{
if (Properties.Settings.Default.IsDebugEnabled)
{
Logger.Error("Error when writing to serial port on button update", ex);

View File

@ -1,9 +1,9 @@
using System.Windows;
using System.Windows.Controls;
namespace WpfMaiTouchEmulator;
namespace WpfMaiTouchEmulator.Managers;
enum TouchValue: long
public enum TouchValue : long
{
A1 = 1 << 0, // 2^0
A2 = 1 << 1, // 2^1
@ -23,7 +23,6 @@ enum TouchValue: long
B8 = 1 << 15, // 2^15
C1 = 1 << 16, // 2^16
C2 = 1 << 17, // 2^17
C3 = C1 | C2, // A special sensor used because center notes are hard to press using a windows touchscreen
D1 = 1 << 18, // 2^18
D2 = 1 << 19, // 2^19
D3 = 1 << 20, // 2^20
@ -50,24 +49,6 @@ internal class MaiTouchSensorButtonStateManager
public MaiTouchSensorButtonStateManager(Label buttonStateValue)
{
this.buttonStateValue = buttonStateValue;
SetupUpdateLoop();
}
private async Task SetupUpdateLoop()
{
string? lastButtonState = null;
while (true)
{
if (lastButtonState != buttonState.ToString())
{
lastButtonState = buttonState.ToString();
Application.Current.Dispatcher.Invoke(() =>
{
buttonStateValue.Content = lastButtonState;
});
}
await Task.Delay(16);
}
}
public void Reset()
@ -77,12 +58,22 @@ internal class MaiTouchSensorButtonStateManager
public void PressButton(TouchValue button)
{
buttonState |= ((long)button);
buttonState |= (long)button;
Application.Current.Dispatcher.Invoke(() =>
{
buttonStateValue.Content = buttonState.ToString();
});
}
public void ReleaseButton(TouchValue button)
{
buttonState &= ~((long)button);
buttonState &= ~(long)button;
Application.Current.Dispatcher.Invoke(() =>
{
buttonStateValue.Content = buttonState.ToString();
});
}
public byte[] GetCurrentState()

View File

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace WpfMaiTouchEmulator.Managers;
public partial class RingButtonEmulator
{
[DllImport("user32.dll")]
static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, nuint dwExtraInfo);
private static readonly IDictionary<TouchValue, byte> touchRingMapping = new Dictionary<TouchValue, byte>()
{
{ TouchValue.A1, 0x57 }, // W
{ TouchValue.A2, 0x45 }, // E
{ TouchValue.A3, 0x44 }, // D
{ TouchValue.A4, 0x43 }, // C
{ TouchValue.A5, 0x58 }, // X
{ TouchValue.A6, 0x5A }, // Z
{ TouchValue.A7, 0x41 }, // A
{ TouchValue.A8, 0x51 }, // Q
};
private const uint KEYEVENTF_KEYUP = 0x0002;
public static bool HasRingButtonMapping(TouchValue touchValue)
{
return touchRingMapping.ContainsKey(touchValue);
}
public static void PressButton(TouchValue touchValue)
{
if (touchRingMapping.TryGetValue(touchValue, out var vk))
{
keybd_event(vk, 0, 0, nuint.Zero);
}
}
public static void ReleaseButton(TouchValue touchValue)
{
if (touchRingMapping.TryGetValue(touchValue, out var vk))
{
keybd_event(vk, 0, KEYEVENTF_KEYUP, nuint.Zero);
}
}
public static void ReleaseAllButtons()
{
foreach (var vk in touchRingMapping.Values)
{
keybd_event(vk, 0, KEYEVENTF_KEYUP, nuint.Zero);
}
}
}

View File

@ -1,16 +1,16 @@
using System.Runtime.InteropServices;
using System.Windows;
namespace WpfMaiTouchEmulator;
namespace WpfMaiTouchEmulator.Managers;
class TouchPanelPositionManager
{
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string? lpClassName, string lpWindowName);
static extern nint FindWindow(string? lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
static extern bool GetWindowRect(nint hWnd, out RECT lpRect);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
@ -29,16 +29,16 @@ class TouchPanelPositionManager
try
{
var hWnd = FindWindow(null, "Sinmai");
if (hWnd != IntPtr.Zero)
if (hWnd != nint.Zero)
{
RECT rect;
if (GetWindowRect(hWnd, out rect))
{
// Calculate the desired size and position based on the other application's window
var renderRect = GetLargest916Rect(rect);
var height = renderRect.Width;
var left = rect.Left + ((rect.Right - rect.Left) - renderRect.Width) / 2; // Center horizontally
var top = rect.Bottom - height;
var height = renderRect.Height;
var left = rect.Left + (rect.Right - rect.Left - renderRect.Width) / 2; // Center horizontally
var top = renderRect.Top;
return new Rect(left, top, renderRect.Width, height);
}
}
@ -56,8 +56,8 @@ class TouchPanelPositionManager
var originalWidth = original.Width;
var originalHeight = original.Height;
var widthBasedHeight = (originalWidth * 16) / 9;
var heightBasedWidth = (originalHeight * 9) / 16;
var widthBasedHeight = originalWidth * 16 / 9;
var heightBasedWidth = originalHeight * 9 / 16;
if (widthBasedHeight <= originalHeight)
{

View File

@ -2,10 +2,17 @@
using System.IO.Ports;
using System.Windows;
namespace WpfMaiTouchEmulator;
namespace WpfMaiTouchEmulator.Managers;
internal class VirtualComPortManager
{
private readonly MainWindowViewModel _viewModel;
public VirtualComPortManager(MainWindowViewModel viewModel)
{
_viewModel = viewModel;
}
public IEnumerable<string> GetInstalledPorts()
{
return SerialPort.GetPortNames();
@ -14,7 +21,7 @@ internal class VirtualComPortManager
public async Task<bool> CheckIfPortInstalled(string port, bool expectToExist)
{
var installed = false;
for (var i = 0; i< 3; i++)
for (var i = 0; i < 3; i++)
{
installed = GetInstalledPorts().Any(x => x == port);
if (installed && expectToExist)
@ -32,7 +39,7 @@ internal class VirtualComPortManager
if (await CheckIfPortInstalled("COM3", false))
{
Logger.Warn("Port COM3 already registered.");
MessageBox.Show("Port COM3 already registered. Either remove it via Device Manager or uninstall the virutal port.");
MessageBox.Show(_viewModel.TxtCom3AlreadyInstalled);
return;
}
try
@ -42,18 +49,18 @@ internal class VirtualComPortManager
if (await CheckIfPortInstalled("COM3", true))
{
Logger.Info("Port COM3 successfully installed.");
MessageBox.Show("Port COM3 successfully installed.");
MessageBox.Show(_viewModel.TxtCom3InstalledSuccessfully);
}
else
{
Logger.Error("Port COM3 failed to install");
MessageBox.Show($"Port COM3 failed to install", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
MessageBox.Show(_viewModel.TxtCom3InstallFailed, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
catch (Exception ex)
{
Logger.Error("Port COM3 failed to install", ex);
MessageBox.Show($"Port COM3 failed to install. {ex}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
MessageBox.Show($"{_viewModel.TxtCom3InstallFailed} {ex}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
@ -63,7 +70,7 @@ internal class VirtualComPortManager
if (!await CheckIfPortInstalled("COM3", true))
{
Logger.Warn("Port COM3 not found. No need to uninstall.");
MessageBox.Show("Port COM3 not found. No need to uninstall.");
MessageBox.Show(_viewModel.TxtCom3UninstallNotRequired);
return;
}
try
@ -73,18 +80,18 @@ internal class VirtualComPortManager
if (!await CheckIfPortInstalled("COM3", false))
{
Logger.Info("Port COM3 successfully uninstalled.");
MessageBox.Show("Port COM3 successfully uninstalled.");
MessageBox.Show(_viewModel.TxtCom3UninstalledSuccessfully);
}
else
{
Logger.Error("Port COM3 failed to uninstall");
MessageBox.Show($"Port COM3 failed to uninstall. It may be a real device, uninstall it from Device Manager", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
MessageBox.Show(_viewModel.TxtCom3UninstallFailed, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
catch (Exception ex)
{
Logger.Error("Port COM3 failed to uninstall", ex);
MessageBox.Show($"Port COM3 failed to uninstall. {ex}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
MessageBox.Show($"{_viewModel.TxtCom3UninstallFailed} {ex}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}

View File

@ -12,7 +12,7 @@ namespace WpfMaiTouchEmulator.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.8.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.12.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
@ -82,5 +82,77 @@ namespace WpfMaiTouchEmulator.Properties {
this["FirstOpen"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string UserLanguage {
get {
return ((string)(this["UserLanguage"]));
}
set {
this["UserLanguage"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("True")]
public bool IsRingButtonEmulationEnabled {
get {
return ((bool)(this["IsRingButtonEmulationEnabled"]));
}
set {
this["IsRingButtonEmulationEnabled"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("0")]
public int BorderSetting {
get {
return ((int)(this["BorderSetting"]));
}
set {
this["BorderSetting"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string BorderColour {
get {
return ((string)(this["BorderColour"]));
}
set {
this["BorderColour"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("True")]
public bool UserSettingsUpgradeRequired {
get {
return ((bool)(this["UserSettingsUpgradeRequired"]));
}
set {
this["UserSettingsUpgradeRequired"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool IsLargeButtonsEnabled {
get {
return ((bool)(this["IsLargeButtonsEnabled"]));
}
set {
this["IsLargeButtonsEnabled"] = value;
}
}
}
}

View File

@ -17,5 +17,23 @@
<Setting Name="FirstOpen" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="UserLanguage" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="IsRingButtonEmulationEnabled" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="BorderSetting" Type="System.Int32" Scope="User">
<Value Profile="(Default)">0</Value>
</Setting>
<Setting Name="BorderColour" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="UserSettingsUpgradeRequired" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="IsLargeButtonsEnabled" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
</Settings>
</SettingsFile>

53
README.jp.md 100644
View File

@ -0,0 +1,53 @@
# WpfMaiTouchEmulator
タッチスクリーン上のソフトウェアを使用してSDEZ MaiMaiのタッチセンサーハードウェアをエミュレートすることを目的としたWPF C# アプリケーションです。
# 動作原理
このアプリはcom0comを使用して仮想COMポートのペアCOM3 - COM23をインストールします。その後、アプリはCOM23に接続し、ハードウェアのタッチセンサーがどのように機能するかをエミュレートし、このポートを通じてデータを送信します。
# デモ
https://github.com/user-attachments/assets/fd0d8f08-586b-416e-8e49-7e389a08d02b
https://github.com/user-attachments/assets/1862e59b-520c-4cd5-adf4-37389a75815d
# セットアップ
Windowsで三本指及び四本指のタッチジェスチャーを無効にするW11の設定 -> Bluetooth & デバイス -> タッチ)
このアプリを動作させるにはCOM3が空いている必要があります。多くの場合、COM3はUSBシリアルデバイスによって既に使用されています。これはデバイスマネージャーを通じてアンインストールする必要があります。
1. デバイスマネージャーを開く
2. 隠しデバイスを表示する。「ビュー」->「隠しデバイスを表示」->「ポート(COM & LPT)」-> COM3デバイスを右クリックし、アンインストール。
maimaiの.iniファイルの[AM]セクションに`DummyTouchPanel=0`を設定します。
WpfMaiTouchEmulator.exeアプリを開き、「COMポートをインストール」ボタンをクリックして仮想ポートをインストールします。アプリの起動時にCOM23ポートに自動的に接続するために「自動ポート接続」をチェックするか、「ポートに接続」ボタンを押します。
仮想ポートをインストールした後、アプリがCOM23にバインドできない場合は、PCを再起動すると通常問題が解決します。
SDEZを開始し、通常通りタッチスクリーンを使用します。
[オプション] アプリケーションを毎回手動で開かなくても済むようにするために、「自動ポート接続」を有効にし、MaiMaiのstart.batファイルに`@echo off`の後に次の行を追加します。`start "" "<WpfMaiTouchEmulator.exe>"`
[オプション] W11を使用していて、左スワイプが時々「ウィジェット」ペインを開く場合は、管理者としてこのコマンドを実行して`winget uninstall "windows web experience pack"`、画面の指示に従います。
# 付属のcom0comプログラムを使用せずに仮想COMポートをインストールする
この未知のアプリに管理者権限を与えたくない場合は、アプリに含まれている仮想comのインストール/削除アクションを使用しないで、手動でNull-modem com0comをオンラインでダウンロード必要なバージョンは2.2.2.0!)して、このコマンドを実行します。
`cd "C:\Program Files (x86)\com0com" && setupc.exe install Portname=COM3 Portname=COM23`で仮想ポートをインストールし、
`cd "C:\Program Files (x86)\com0com" && setupc.exe uninstall`で仮想ポートをアンインストールします。
# なぜ?
ほとんどのSDEZは内蔵のタッチスクリーンと非常に互換性が低く、SDEZのソースコードを他の人が変更して内蔵タッチコントロールを機能させるか、.dllファイルを編集して再コンパイルする必要があります。このアプリは、すべてのSDEZバージョンで知る限りではすぐに動作するはずです。
もう一つの理由は、タッチセンサーとして使用するITOエッチングパネルの調達が難しく、かなり高価であるためです。
# パフォーマンス
まあまあです。アプリは最大1Khzでタッチセンサーウィンドウをポーリングしますが、C#で実行されているため、実際の速度はホストマシンのパフォーマンスに依存する場合があります。
精度については、私はそれほど上手ではないので、高レベルのプレイでどれほどうまく機能するかは言えませんが、私の経験ではかなりうまく機能します。
# 感謝
基本的なシリアルデータ形式とタッチ実装の研究について、[whowechina - mai_pico](https://github.com/whowechina/mai_pico) と [Sucareto - Mai2Touch](https://github.com/Sucareto/Mai2Touch) に感謝します。

View File

@ -1,18 +1,23 @@

# WpfMaiTouchEmulator
A WPF C# app meant to emulate the touch sensor hardware for SDEZ MaiMai using software on a touchscreen.
## README in Other Languages
- [简体中文](README.zh.md)
- [日本語](README.jp.md)
# How it works
The app uses com0com to install a virtual COM port pairing of COM3 - COM23. The app then connects to COM23 and emulates how the hardware touch sensor works, sending data over this port.
# Demo
https://github.com/Leapward-Koex/MaiTouchSensorEmulator/assets/30615050/eea21285-fec5-4393-a75d-74274b39789f
https://github.com/user-attachments/assets/fd0d8f08-586b-416e-8e49-7e389a08d02b
https://github.com/Leapward-Koex/MaiTouchSensorEmulator/assets/30615050/e45ca02f-8d82-4ad4-a610-ef08abcb5087
https://github.com/user-attachments/assets/1862e59b-520c-4cd5-adf4-37389a75815d

53
README.zh.md 100644
View File

@ -0,0 +1,53 @@
# WpfMaiTouchEmulator
一个用于通过触摸屏软件模拟SDEZ MaiMai的触摸传感器硬件的WPF C# 应用程序。
# 工作原理
该应用程序使用com0com安装一个虚拟的COM端口配对COM3 - COM23。然后应用程序连接到COM23并模拟硬件触摸传感器的工作方式通过此端口发送数据。
# 演示
https://github.com/user-attachments/assets/fd0d8f08-586b-416e-8e49-7e389a08d02b
https://github.com/user-attachments/assets/1862e59b-520c-4cd5-adf4-37389a75815d
# 设置
在Windows中禁用三指和四指触摸手势在W11设置 -> 蓝牙与设备 -> 触摸)
COM3需要空闲因为这是SDEZ使用的端口。默认情况下大多数人会发现COM3已被USB串行设备使用。需要通过设备管理器卸载
1. 打开设备管理器
2. 显示隐藏设备。"视图" -> "显示隐藏设备" -> "端口COM & LPT" -> 右键点击COM3设备并卸载。
在您的maimai.ini文件的[AM]部分设置`DummyTouchPanel=0`。
打开WpfMaiTouchEmulator.exe应用程序通过"安装COM端口"按钮安装虚拟端口,可以勾选"自动连接端口"使应用程序在启动时连接到COM23端口或按"连接到端口"按钮。
如果在安装虚拟端口后应用程序无法绑定到COM23我发现重新启动我的电脑通常可以解决问题。
启动SDEZ并正常使用您的触摸屏。
[可选] 为了避免每次都手动打开应用程序,您可以启用"自动连接端口"并在您的MaiMai的start.bat文件中`@echo off`之后添加这行代码。`start "" "<您的WpfMaiTouchEmulator.exe路径>"`
[可选] 如果您使用的是W11并且发现左滑有时会打开“小部件”面板您可以通过以管理员身份运行此命令`winget uninstall "windows web experience pack"`并按照屏幕上的指示操作来移除面板。
# 不使用随附的com0com程序安装虚拟COM端口
如果您不想使用应用程序中包含的安装/移除虚拟com操作因为您不想给这个未知的应用程序管理员权限那么您可以通过手动下载Null-modem com0com需要版本2.2.2.0!)并运行此命令来达到同样的效果。
`cd "C:\Program Files (x86)\com0com" && setupc.exe install Portname=COM3 Portname=COM23`来安装虚拟端口和
`cd "C:\Program Files (x86)\com0com" && setupc.exe uninstall`来卸载虚拟端口。
# 为什么?
大多数SDEZ与内置触摸屏的兼容性不是很好需要其他人修改SDEZ的源代码才能使内置触控功能正常工作或者您需要编辑并重新编译.dll文件。这个应用程序应该可以开箱即用地与所有SDEZ版本兼容据我所知
另一个原因是获取用作触摸传感器的ITO蚀刻面板可能既困难又昂贵。
# 性能
还可以应用程序以最高1Khz的频率轮询触摸传感器窗口但它是用C#运行的,所以实际运行速度可能取决于主机的性能。
至于准确性。我不是很擅长,所以无法说它在高级别的游戏中表现如何,但根据我的经验,它运行得相当不错。
# 感谢
感谢 [whowechina - mai_pico](https://github.com/whowechina/mai_pico) 和 [Sucareto - Mai2Touch](https://github.com/Sucareto/Mai2Touch) 提供基本的串行数据格式和触摸实现研究。

Binary file not shown.

Binary file not shown.

499
Resources/Strings.Designer.cs generated 100644
View File

@ -0,0 +1,499 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace WpfMaiTouchEmulator.Resources {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Strings {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Strings() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WpfMaiTouchEmulator.Resources.Strings", typeof(Strings).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to About.
/// </summary>
internal static string LbAbout {
get {
return ResourceManager.GetString("LbAbout", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Automatic port connecting.
/// </summary>
internal static string lbAutoPortConnecting {
get {
return ResourceManager.GetString("lbAutoPortConnecting", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Try to automatically connect to COM23 to send touch sensor updates on app start.
/// </summary>
internal static string lbAutoPortConnectingTT {
get {
return ResourceManager.GetString("lbAutoPortConnectingTT", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Automatic sensor window positioning.
/// </summary>
internal static string lbAutoSensorPositioning {
get {
return ResourceManager.GetString("lbAutoSensorPositioning", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Try and automatically move the touch sensor window into the correct position on top of Sinmai.exe.
/// </summary>
internal static string lbAutoSensorPositioningTT {
get {
return ResourceManager.GetString("lbAutoSensorPositioningTT", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Disabled.
/// </summary>
internal static string LbBorderDisabled {
get {
return ResourceManager.GetString("LbBorderDisabled", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Rainbow.
/// </summary>
internal static string LbBorderRainbow {
get {
return ResourceManager.GetString("LbBorderRainbow", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Border Settings.
/// </summary>
internal static string LbBorderSettings {
get {
return ResourceManager.GetString("LbBorderSettings", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Solid.
/// </summary>
internal static string LbBorderSolid {
get {
return ResourceManager.GetString("LbBorderSolid", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Button State.
/// </summary>
internal static string lbButtonState {
get {
return ResourceManager.GetString("lbButtonState", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to COM Port.
/// </summary>
internal static string LbComPort {
get {
return ResourceManager.GetString("LbComPort", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Connection state.
/// </summary>
internal static string lbConnectionState {
get {
return ResourceManager.GetString("lbConnectionState", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Not connected.
/// </summary>
internal static string lbConnectionStateNotConnected {
get {
return ResourceManager.GetString("lbConnectionStateNotConnected", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Connect to port.
/// </summary>
internal static string lbConnectToPort {
get {
return ResourceManager.GetString("lbConnectToPort", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Debug mode.
/// </summary>
internal static string lbDebugMode {
get {
return ResourceManager.GetString("lbDebugMode", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Show the touchpad with translucency and show inputs highlighted.
/// </summary>
internal static string lbDebugModeTT {
get {
return ResourceManager.GetString("lbDebugModeTT", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Emulate ring buttons.
/// </summary>
internal static string lbEmulateRingButtons {
get {
return ResourceManager.GetString("lbEmulateRingButtons", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Useful when navigating the menus without a keyboard.
/// </summary>
internal static string lbEmulateRingButtonsTT {
get {
return ResourceManager.GetString("lbEmulateRingButtonsTT", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Exit when Sinmai exits.
/// </summary>
internal static string lbExitWithSinmai {
get {
return ResourceManager.GetString("lbExitWithSinmai", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Try and detect when Sinmai.exe exits and exit this app alongside it.
/// </summary>
internal static string lbExitWithSinmaiTT {
get {
return ResourceManager.GetString("lbExitWithSinmaiTT", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Install com port.
/// </summary>
internal static string lbInstallComPort {
get {
return ResourceManager.GetString("lbInstallComPort", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Language.
/// </summary>
internal static string lbLanguageDropdown {
get {
return ResourceManager.GetString("lbLanguageDropdown", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Extend ring button size.
/// </summary>
internal static string lbLargeButtons {
get {
return ResourceManager.GetString("lbLargeButtons", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Extends the ring button hitboxes to the edge of the window.
/// </summary>
internal static string lbLargeButtonsTT {
get {
return ResourceManager.GetString("lbLargeButtonsTT", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to List installed com ports.
/// </summary>
internal static string lbListComPorts {
get {
return ResourceManager.GetString("lbListComPorts", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to _Help.
/// </summary>
internal static string lbMenuCategoryHelp {
get {
return ResourceManager.GetString("lbMenuCategoryHelp", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Show setup instructions.
/// </summary>
internal static string lbMenuItemSetup {
get {
return ResourceManager.GetString("lbMenuItemSetup", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open log folder.
/// </summary>
internal static string LbOpenLogFolder {
get {
return ResourceManager.GetString("LbOpenLogFolder", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Received.
/// </summary>
internal static string lbRecievedData {
get {
return ResourceManager.GetString("lbRecievedData", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Sent.
/// </summary>
internal static string lbSentData {
get {
return ResourceManager.GetString("lbSentData", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Settings.
/// </summary>
internal static string LbSettings {
get {
return ResourceManager.GetString("LbSettings", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Drag.
/// </summary>
internal static string LbTouchPanelDrag {
get {
return ResourceManager.GetString("LbTouchPanelDrag", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Resize.
/// </summary>
internal static string LbTouchPanelResize {
get {
return ResourceManager.GetString("LbTouchPanelResize", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Uninstall com port.
/// </summary>
internal static string lbUninstallComPort {
get {
return ResourceManager.GetString("lbUninstallComPort", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Port COM3 already registered. Either remove it via Device Manager or uninstall the virtual port..
/// </summary>
internal static string TxtCom3AlreadyInstalled {
get {
return ResourceManager.GetString("TxtCom3AlreadyInstalled", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Port COM3 successfully installed..
/// </summary>
internal static string TxtCom3InstalledSuccessfully {
get {
return ResourceManager.GetString("TxtCom3InstalledSuccessfully", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Port COM3 failed to install.
/// </summary>
internal static string TxtCom3InstallFailed {
get {
return ResourceManager.GetString("TxtCom3InstallFailed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Port COM3 successfully uninstalled..
/// </summary>
internal static string TxtCom3UninstalledSuccessfully {
get {
return ResourceManager.GetString("TxtCom3UninstalledSuccessfully", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Port COM3 failed to uninstall. It may be a real device, uninstall it from Device Manager.
/// </summary>
internal static string TxtCom3UninstallFailed {
get {
return ResourceManager.GetString("TxtCom3UninstallFailed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Port COM3 not found. No need to uninstall..
/// </summary>
internal static string TxtCom3UninstallNotRequired {
get {
return ResourceManager.GetString("TxtCom3UninstallNotRequired", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Connected to port.
/// </summary>
internal static string TxtComPortConnected {
get {
return ResourceManager.GetString("TxtComPortConnected", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Conecting....
/// </summary>
internal static string TxtComPortConnecting {
get {
return ResourceManager.GetString("TxtComPortConnecting", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Installed ports.
/// </summary>
internal static string TxtCurrentlyInstalledPorts {
get {
return ResourceManager.GetString("TxtCurrentlyInstalledPorts", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Error connecting to COM port.
/// </summary>
internal static string TxtErrorConnectingToPortHeader {
get {
return ResourceManager.GetString("TxtErrorConnectingToPortHeader", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Failed to listen for Sinmai exit signal, this can happen when Sinmai.exe is being run as admin.\n\nAutomatic exiting disabled..
/// </summary>
internal static string TxtFailedToSetupSinmaiExit {
get {
return ResourceManager.GetString("TxtFailedToSetupSinmaiExit", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Failed to listen for Sinmai exit.
/// </summary>
internal static string TxtFailedToSetupSinmaiExitHeader {
get {
return ResourceManager.GetString("TxtFailedToSetupSinmaiExitHeader", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Please remove any COM devices using the COM3 port before installing the virtual COM port. In Device Manager click &quot;View&quot; then enabled &quot;Show hidden devices&quot; and uninstall any devices that are using the COM3 port.
///
///After ensuring COM3 is free please use the install COM port button in the app to register the app.
///
///The app needs to connect to the port prior to Sinmai.exe being opened..
/// </summary>
internal static string TxtSetupInstructions {
get {
return ResourceManager.GetString("TxtSetupInstructions", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to First time setup.
/// </summary>
internal static string TxtSetupInstructionsHeader {
get {
return ResourceManager.GetString("TxtSetupInstructionsHeader", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,268 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="LbAbout" xml:space="preserve">
<value>について</value>
</data>
<data name="lbAutoPortConnecting" xml:space="preserve">
<value>自動ポート接続</value>
</data>
<data name="lbAutoPortConnectingTT" xml:space="preserve">
<value>アプリ起動時にCOM23に自動的に接続してタッチセンサーの更新を送信しようとする</value>
</data>
<data name="lbAutoSensorPositioning" xml:space="preserve">
<value>自動センサーウィンドウ配置</value>
</data>
<data name="lbAutoSensorPositioningTT" xml:space="preserve">
<value>Sinmai.exeの上に正しい位置にタッチセンサーウィンドウを自動的に移動しようとする</value>
</data>
<data name="LbBorderDisabled" xml:space="preserve">
<value>無効</value>
</data>
<data name="LbBorderRainbow" xml:space="preserve">
<value>レインボー</value>
</data>
<data name="LbBorderSettings" xml:space="preserve">
<value>境界線設定</value>
</data>
<data name="LbBorderSolid" xml:space="preserve">
<value>単色</value>
</data>
<data name="lbButtonState" xml:space="preserve">
<value>ボタン状態</value>
</data>
<data name="LbComPort" xml:space="preserve">
<value>COMポート</value>
</data>
<data name="lbConnectionState" xml:space="preserve">
<value>接続状態</value>
</data>
<data name="lbConnectionStateNotConnected" xml:space="preserve">
<value>未接続</value>
</data>
<data name="lbConnectToPort" xml:space="preserve">
<value>ポートに接続</value>
</data>
<data name="lbDebugMode" xml:space="preserve">
<value>デバッグモード</value>
</data>
<data name="lbDebugModeTT" xml:space="preserve">
<value>タッチパッドを半透明で表示し、入力をハイライト表示</value>
</data>
<data name="lbEmulateRingButtons" xml:space="preserve">
<value>リングボタンをエミュレートする</value>
</data>
<data name="lbEmulateRingButtonsTT" xml:space="preserve">
<value>キーボードがないときにメニュー操作に便利</value>
</data>
<data name="lbExitWithSinmai" xml:space="preserve">
<value>Sinmai終了時に終了</value>
</data>
<data name="lbExitWithSinmaiTT" xml:space="preserve">
<value>Sinmai.exeが終了するのを検出し、このアプリも同時に終了しようとする</value>
</data>
<data name="lbInstallComPort" xml:space="preserve">
<value>COMポートをインストール</value>
</data>
<data name="lbLanguageDropdown" xml:space="preserve">
<value>言語</value>
</data>
<data name="lbLargeButtons" xml:space="preserve">
<value>リングボタンのサイズを拡大</value>
</data>
<data name="lbLargeButtonsTT" xml:space="preserve">
<value>リングボタンのヒットボックスをウィンドウの端まで拡大</value>
</data>
<data name="lbListComPorts" xml:space="preserve">
<value>インストールされたCOMポートをリストアップ</value>
</data>
<data name="lbMenuCategoryHelp" xml:space="preserve">
<value>_ヘルプ</value>
</data>
<data name="lbMenuItemSetup" xml:space="preserve">
<value>セットアップ指示を表示</value>
</data>
<data name="LbOpenLogFolder" xml:space="preserve">
<value>ログフォルダを開く</value>
</data>
<data name="lbRecievedData" xml:space="preserve">
<value>受信データ</value>
</data>
<data name="lbSentData" xml:space="preserve">
<value>送信データ</value>
</data>
<data name="LbSettings" xml:space="preserve">
<value>設定</value>
</data>
<data name="LbTouchPanelDrag" xml:space="preserve">
<value>ドラッグ</value>
</data>
<data name="LbTouchPanelResize" xml:space="preserve">
<value>サイズ変更</value>
</data>
<data name="lbUninstallComPort" xml:space="preserve">
<value>COMポートをアンインストール</value>
</data>
<data name="TxtCom3AlreadyInstalled" xml:space="preserve">
<value>ポートCOM3はすでに登録されています。デバイスマネージャを通じてそれを取り除くか、仮想ポートをアンインストールしてください。</value>
</data>
<data name="TxtCom3InstalledSuccessfully" xml:space="preserve">
<value>COM3ポートが正常にインストールされました。</value>
</data>
<data name="TxtCom3InstallFailed" xml:space="preserve">
<value>COM3ポートのインストールに失敗しました</value>
</data>
<data name="TxtCom3UninstalledSuccessfully" xml:space="preserve">
<value>COM3ポートが正常にアンインストールされました。</value>
</data>
<data name="TxtCom3UninstallFailed" xml:space="preserve">
<value>COM3ポートのアンインストールに失敗しました。実際のデバイスである可能性があります、デバイスマネージャーからアンインストールしてください</value>
</data>
<data name="TxtCom3UninstallNotRequired" xml:space="preserve">
<value>COM3ポートが見つかりませんでした。アンインストールの必要はありません。</value>
</data>
<data name="TxtComPortConnected" xml:space="preserve">
<value>ポートに接続されました</value>
</data>
<data name="TxtComPortConnecting" xml:space="preserve">
<value>接続中...</value>
</data>
<data name="TxtCurrentlyInstalledPorts" xml:space="preserve">
<value>インストールされたポート</value>
</data>
<data name="TxtErrorConnectingToPortHeader" xml:space="preserve">
<value>COMポートへの接続エラー</value>
</data>
<data name="TxtFailedToSetupSinmaiExit" xml:space="preserve">
<value>Sinmaiの終了信号を聞くことができなかったため、これはSinmai.exeが管理者として実行されているときに起こり得ます。\n\n自動終了が無効になりました。</value>
</data>
<data name="TxtFailedToSetupSinmaiExitHeader" xml:space="preserve">
<value>Sinmaiの終了を検出できなかった</value>
</data>
<data name="TxtSetupInstructions" xml:space="preserve">
<value>COM3ポートを使用している任意のCOMデバイスを仮想COMポートをインストールする前に取り除いてください。デバイスマネージャで「表示」をクリックし、「隠れたデバイスを表示」を有効にして、COM3ポートを使用しているデバイスをアンインストールしてください。
COM3が空いていることを確認した後、アプリ内のCOMポートインストールボタンを使用してアプリを登録してください
アプリはSinmai.exeを開く前にポートに接続する必要があります。</value>
</data>
<data name="TxtSetupInstructionsHeader" xml:space="preserve">
<value>初回セットアップ</value>
</data>
</root>

View File

@ -0,0 +1,268 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="LbAbout" xml:space="preserve">
<value>About</value>
</data>
<data name="lbAutoPortConnecting" xml:space="preserve">
<value>Automatic port connecting</value>
</data>
<data name="lbAutoPortConnectingTT" xml:space="preserve">
<value>Try to automatically connect to COM23 to send touch sensor updates on app start</value>
</data>
<data name="lbAutoSensorPositioning" xml:space="preserve">
<value>Automatic sensor window positioning</value>
</data>
<data name="lbAutoSensorPositioningTT" xml:space="preserve">
<value>Try and automatically move the touch sensor window into the correct position on top of Sinmai.exe</value>
</data>
<data name="LbBorderDisabled" xml:space="preserve">
<value>Disabled</value>
</data>
<data name="LbBorderRainbow" xml:space="preserve">
<value>Rainbow</value>
</data>
<data name="LbBorderSettings" xml:space="preserve">
<value>Border Settings</value>
</data>
<data name="LbBorderSolid" xml:space="preserve">
<value>Solid</value>
</data>
<data name="lbButtonState" xml:space="preserve">
<value>Button State</value>
</data>
<data name="LbComPort" xml:space="preserve">
<value>COM Port</value>
</data>
<data name="lbConnectionState" xml:space="preserve">
<value>Connection state</value>
</data>
<data name="lbConnectionStateNotConnected" xml:space="preserve">
<value>Not connected</value>
</data>
<data name="lbConnectToPort" xml:space="preserve">
<value>Connect to port</value>
</data>
<data name="lbDebugMode" xml:space="preserve">
<value>Debug mode</value>
</data>
<data name="lbDebugModeTT" xml:space="preserve">
<value>Show the touchpad with translucency and show inputs highlighted</value>
</data>
<data name="lbEmulateRingButtons" xml:space="preserve">
<value>Emulate ring buttons</value>
</data>
<data name="lbEmulateRingButtonsTT" xml:space="preserve">
<value>Useful when navigating the menus without a keyboard</value>
</data>
<data name="lbExitWithSinmai" xml:space="preserve">
<value>Exit when Sinmai exits</value>
</data>
<data name="lbExitWithSinmaiTT" xml:space="preserve">
<value>Try and detect when Sinmai.exe exits and exit this app alongside it</value>
</data>
<data name="lbInstallComPort" xml:space="preserve">
<value>Install com port</value>
</data>
<data name="lbLanguageDropdown" xml:space="preserve">
<value>Language</value>
</data>
<data name="lbLargeButtons" xml:space="preserve">
<value>Extend ring button size</value>
</data>
<data name="lbLargeButtonsTT" xml:space="preserve">
<value>Extends the ring button hitboxes to the edge of the window</value>
</data>
<data name="lbListComPorts" xml:space="preserve">
<value>List installed com ports</value>
</data>
<data name="lbMenuCategoryHelp" xml:space="preserve">
<value>_Help</value>
</data>
<data name="lbMenuItemSetup" xml:space="preserve">
<value>Show setup instructions</value>
</data>
<data name="LbOpenLogFolder" xml:space="preserve">
<value>Open log folder</value>
</data>
<data name="lbRecievedData" xml:space="preserve">
<value>Received</value>
</data>
<data name="lbSentData" xml:space="preserve">
<value>Sent</value>
</data>
<data name="LbSettings" xml:space="preserve">
<value>Settings</value>
</data>
<data name="LbTouchPanelDrag" xml:space="preserve">
<value>Drag</value>
</data>
<data name="LbTouchPanelResize" xml:space="preserve">
<value>Resize</value>
</data>
<data name="lbUninstallComPort" xml:space="preserve">
<value>Uninstall com port</value>
</data>
<data name="TxtCom3AlreadyInstalled" xml:space="preserve">
<value>Port COM3 already registered. Either remove it via Device Manager or uninstall the virtual port.</value>
</data>
<data name="TxtCom3InstalledSuccessfully" xml:space="preserve">
<value>Port COM3 successfully installed.</value>
</data>
<data name="TxtCom3InstallFailed" xml:space="preserve">
<value>Port COM3 failed to install</value>
</data>
<data name="TxtCom3UninstalledSuccessfully" xml:space="preserve">
<value>Port COM3 successfully uninstalled.</value>
</data>
<data name="TxtCom3UninstallFailed" xml:space="preserve">
<value>Port COM3 failed to uninstall. It may be a real device, uninstall it from Device Manager</value>
</data>
<data name="TxtCom3UninstallNotRequired" xml:space="preserve">
<value>Port COM3 not found. No need to uninstall.</value>
</data>
<data name="TxtComPortConnected" xml:space="preserve">
<value>Connected to port</value>
</data>
<data name="TxtComPortConnecting" xml:space="preserve">
<value>Conecting...</value>
</data>
<data name="TxtCurrentlyInstalledPorts" xml:space="preserve">
<value>Installed ports</value>
</data>
<data name="TxtErrorConnectingToPortHeader" xml:space="preserve">
<value>Error connecting to COM port</value>
</data>
<data name="TxtFailedToSetupSinmaiExit" xml:space="preserve">
<value>Failed to listen for Sinmai exit signal, this can happen when Sinmai.exe is being run as admin.\n\nAutomatic exiting disabled.</value>
</data>
<data name="TxtFailedToSetupSinmaiExitHeader" xml:space="preserve">
<value>Failed to listen for Sinmai exit</value>
</data>
<data name="TxtSetupInstructions" xml:space="preserve">
<value>Please remove any COM devices using the COM3 port before installing the virtual COM port. In Device Manager click "View" then enabled "Show hidden devices" and uninstall any devices that are using the COM3 port.
After ensuring COM3 is free please use the install COM port button in the app to register the app.
The app needs to connect to the port prior to Sinmai.exe being opened.</value>
</data>
<data name="TxtSetupInstructionsHeader" xml:space="preserve">
<value>First time setup</value>
</data>
</root>

View File

@ -0,0 +1,268 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="LbAbout" xml:space="preserve">
<value>关于</value>
</data>
<data name="lbAutoPortConnecting" xml:space="preserve">
<value>自动连接端口</value>
</data>
<data name="lbAutoPortConnectingTT" xml:space="preserve">
<value>尝试在应用启动时自动连接到COM23以发送触摸传感器更新</value>
</data>
<data name="lbAutoSensorPositioning" xml:space="preserve">
<value>自动传感器窗口定位</value>
</data>
<data name="lbAutoSensorPositioningTT" xml:space="preserve">
<value>尝试自动移动触摸传感器窗口到Sinmai.exe上方的正确位置</value>
</data>
<data name="LbBorderDisabled" xml:space="preserve">
<value>無効</value>
</data>
<data name="LbBorderRainbow" xml:space="preserve">
<value>彩虹</value>
</data>
<data name="LbBorderSettings" xml:space="preserve">
<value>境界線設定</value>
</data>
<data name="LbBorderSolid" xml:space="preserve">
<value>纯色</value>
</data>
<data name="lbButtonState" xml:space="preserve">
<value>按钮状态</value>
</data>
<data name="LbComPort" xml:space="preserve">
<value>COM端口</value>
</data>
<data name="lbConnectionState" xml:space="preserve">
<value>连接状态</value>
</data>
<data name="lbConnectionStateNotConnected" xml:space="preserve">
<value>未连接</value>
</data>
<data name="lbConnectToPort" xml:space="preserve">
<value>连接到端口</value>
</data>
<data name="lbDebugMode" xml:space="preserve">
<value>调试模式</value>
</data>
<data name="lbDebugModeTT" xml:space="preserve">
<value>显示半透明的触控板并突出显示输入</value>
</data>
<data name="lbEmulateRingButtons" xml:space="preserve">
<value>模拟环形按钮</value>
</data>
<data name="lbEmulateRingButtonsTT" xml:space="preserve">
<value>在没有键盘的情况下导航菜单时很有用</value>
</data>
<data name="lbExitWithSinmai" xml:space="preserve">
<value>随Sinmai退出</value>
</data>
<data name="lbExitWithSinmaiTT" xml:space="preserve">
<value>尝试检测Sinmai.exe何时退出并与之同时退出此应用</value>
</data>
<data name="lbInstallComPort" xml:space="preserve">
<value>安装COM端口</value>
</data>
<data name="lbLanguageDropdown" xml:space="preserve">
<value>语言</value>
</data>
<data name="lbLargeButtons" xml:space="preserve">
<value>扩大环形按钮大小</value>
</data>
<data name="lbLargeButtonsTT" xml:space="preserve">
<value>将环形按钮的碰撞盒扩展到窗口边缘</value>
</data>
<data name="lbListComPorts" xml:space="preserve">
<value>列出已安装的COM端口</value>
</data>
<data name="lbMenuCategoryHelp" xml:space="preserve">
<value>_帮助</value>
</data>
<data name="lbMenuItemSetup" xml:space="preserve">
<value>显示设置说明</value>
</data>
<data name="LbOpenLogFolder" xml:space="preserve">
<value>打开日志文件夹</value>
</data>
<data name="lbRecievedData" xml:space="preserve">
<value>已接收数据</value>
</data>
<data name="lbSentData" xml:space="preserve">
<value>已发送数据</value>
</data>
<data name="LbSettings" xml:space="preserve">
<value>设置</value>
</data>
<data name="LbTouchPanelDrag" xml:space="preserve">
<value>拖动</value>
</data>
<data name="LbTouchPanelResize" xml:space="preserve">
<value>调整大小</value>
</data>
<data name="lbUninstallComPort" xml:space="preserve">
<value>卸载COM端口</value>
</data>
<data name="TxtCom3AlreadyInstalled" xml:space="preserve">
<value>COM3端口已注册。可通过设备管理器移除或卸载虚拟端口。</value>
</data>
<data name="TxtCom3InstalledSuccessfully" xml:space="preserve">
<value>COM3端口成功安装。</value>
</data>
<data name="TxtCom3InstallFailed" xml:space="preserve">
<value>COM3端口安装失败</value>
</data>
<data name="TxtCom3UninstalledSuccessfully" xml:space="preserve">
<value>COM3端口成功卸载。</value>
</data>
<data name="TxtCom3UninstallFailed" xml:space="preserve">
<value>COM3端口卸载失败。可能是真实设备请从设备管理器中卸载</value>
</data>
<data name="TxtCom3UninstallNotRequired" xml:space="preserve">
<value>未找到COM3端口无需卸载。</value>
</data>
<data name="TxtComPortConnected" xml:space="preserve">
<value>已连接到端口</value>
</data>
<data name="TxtComPortConnecting" xml:space="preserve">
<value>正在连接...</value>
</data>
<data name="TxtCurrentlyInstalledPorts" xml:space="preserve">
<value>已安装端口</value>
</data>
<data name="TxtErrorConnectingToPortHeader" xml:space="preserve">
<value>连接COM端口错误</value>
</data>
<data name="TxtFailedToSetupSinmaiExit" xml:space="preserve">
<value>无法监听Sinmai退出信号这可能是因为Sinmai.exe以管理员身份运行。\n\n自动退出功能已禁用。</value>
</data>
<data name="TxtFailedToSetupSinmaiExitHeader" xml:space="preserve">
<value>监听Sinmai退出失败</value>
</data>
<data name="TxtSetupInstructions" xml:space="preserve">
<value>请在安装虚拟COM端口之前移除使用COM3端口的任何COM设备。在设备管理器中点击“查看”然后启用“显示隐藏的设备”并卸载任何使用COM3端口的设备。
确保COM3端口空闲后请使用应用中的安装COM端口按钮来注册应用。
应用需要在打开Sinmai.exe之前连接到端口。</value>
</data>
<data name="TxtSetupInstructionsHeader" xml:space="preserve">
<value>首次设置</value>
</data>
</root>

View File

@ -3,69 +3,121 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfMaiTouchEmulator"
xmlns:local="clr-namespace:WpfMaiTouchEmulator.Managers"
mc:Ignorable="d"
Title="TouchPanel" Height="800" Width="800"
Title="TouchPanel" Height="1280" Width="720"
ResizeMode="NoResize"
AllowsTransparency="True" WindowStyle="None" Background="Transparent"
>
<Window.Resources>
<Style x:Key="DraggableHandleStyle" TargetType="Border">
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#FFEEEEEE" Offset="0"/>
<GradientStop Color="#FFCCCCCC" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="CornerRadius" Value="10"/>
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect Color="Black" BlurRadius="5" ShadowDepth="2"/>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ResizeGripStyle" TargetType="Border">
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#FFCCCCCC" Offset="0"/>
<GradientStop Color="#FFAAAAAA" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="CornerRadius" Value="10"/>
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect Color="Black" BlurRadius="5" ShadowDepth="2"/>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Border Background="#01000000">
<StackPanel>
<Viewbox Stretch="Uniform" Opacity="1">
<Canvas x:Name="TouchCanvas" Width="1440" Height="1440">
<Border x:Name="DragWindowHandle" HorizontalAlignment="Center" VerticalAlignment="Top"
Height="95" Background="White" MouseLeftButtonDown="DragBar_MouseLeftButtonDown"
Cursor="SizeAll" Width="130">
<Label FontSize="50" Content="Drag" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border x:Name="DragWindowHandle"
HorizontalAlignment="Left" VerticalAlignment="Top"
Height="50" Width="100"
Style="{StaticResource DraggableHandleStyle}"
MouseLeftButtonDown="DragBar_MouseLeftButtonDown"
Cursor="SizeAll">
<TextBlock FontSize="24" Text="{Binding LbTouchPanelDrag}"
VerticalAlignment="Center" HorizontalAlignment="Center" d:Text="Drag"/>
</Border>
<DockPanel VerticalAlignment="Bottom">
<Viewbox Stretch="Uniform" Opacity="1">
<Border x:Name="touchPanelBorder" BorderThickness="10"
CornerRadius="720"
Width="1440" VerticalAlignment="Bottom">
<Canvas x:Name="TouchCanvas" Width="1440" Height="1440" Margin="-10,-10,-10,-10">
<Polygon Canvas.Left="620" Canvas.Top="6" Points="0,5 50,2 100,0 150,2 200,5 165,253 100,188 35,253" Tag="{x:Static local:TouchValue.D1}" Fill="White" />
<Polygon Canvas.Left="786" Canvas.Top="11" Points="150,28 245,65 360,133 208,338 145,338 49,297 0,249 35,0" Tag="{x:Static local:TouchValue.A1}" Fill="White" />
<Polygon Canvas.Left="995" Canvas.Top="144" Points="153,0 187,32 225,67 259,104 295,147 96,297 96,205 0,205" Tag="{x:Static local:TouchValue.D2}" Fill="White" />
<Polygon Canvas.Left="1091" Canvas.Top="292" Points="261,101 303,195 339,327 91,362 42,314 0,219 0,150 202,0" Tag="{x:Static local:TouchValue.A2}" Fill="White" />
<Polygon Canvas.Left="1182" Canvas.Top="620" Points="248,0 251,48 253,100 251,150 247,199 0,165 65,100 0,35" Tag="{x:Static local:TouchValue.D3}" Fill="White" />
<Polygon Canvas.Left="1092" Canvas.Top="786" Points="305,150 269,246 201,364 0,213 0,144 41,48 89,0 337,34" Tag="{x:Static local:TouchValue.A3}" Fill="White" />
<Polygon Canvas.Left="1000" Canvas.Top="1000" Points="292,151 260,187 225,225 188,259 151,291 0,92 92,92 92,0" Tag="{x:Static local:TouchValue.D4}" Fill="White" />
<Polygon Canvas.Left="786" Canvas.Top="1092" Points="260,259 167,301 37,335 0,83 48,35 144,0 212,0 364,200" Tag="{x:Static local:TouchValue.A4}" Fill="White" />
<Polygon Canvas.Left="621" Canvas.Top="1175" Points="199,252 151,255 99,257 49,255 0,252 34,0 99,65 164,0" Tag="{x:Static local:TouchValue.D5}" Fill="White" />
<Polygon Canvas.Left="291" Canvas.Top="1092" Points="104,259 197,301 327,335 363,83 316,35 220,0 152,0 0,201" Tag="{x:Static local:TouchValue.A5}" Fill="White" />
<Polygon Canvas.Left="150" Canvas.Top="1000" Points="140,292 104,260 66,225 32,188 0,151 199,0 199,92 291,92" Tag="{x:Static local:TouchValue.D6}" Fill="White" />
<Polygon Canvas.Left="16" Canvas.Top="785" Points="32,150 68,246 133,365 333,214 333,144 296,48 248,0 0,35" Tag="{x:Static local:TouchValue.A6}" Fill="White" />
<Polygon Canvas.Left="10" Canvas.Top="620" Points="5,199 2,151 0,99 2,49 6,0 253,34 188,99 253,164" Tag="{x:Static local:TouchValue.D7}" Fill="White" />
<Polygon Canvas.Left="16" Canvas.Top="291" Points="78,101 36,195 0,327 248,362 297,314 333,219 333,151 132,0" Tag="{x:Static local:TouchValue.A7}" Fill="White" />
<Polygon Canvas.Left="149" Canvas.Top="150" Points="0,140 32,104 67,66 104,32 145,0 298,199 200,199 200,291" Tag="{x:Static local:TouchValue.D8}" Fill="White" />
<Polygon Canvas.Left="295" Canvas.Top="11" Points="210,28 115,65 0,138 153,338 215,338 311,297 359,249 324,0" Tag="{x:Static local:TouchValue.A8}" Fill="White" />
<Polygon Canvas.Left="607" Canvas.Top="195" Points="0,113 113,0 226,113 113,226" Tag="{x:Static local:TouchValue.E1}" Fill="White" />
<Polygon Canvas.Left="720" Canvas.Top="346" Points="0,78 78,0 209,55 209,165 180,195 70,195 0,130" Tag="{x:Static local:TouchValue.B1}" Fill="White" />
<Polygon Canvas.Left="930" Canvas.Top="350" Points="0,0 0,160 160,160, 160,0 0,0" Tag="{x:Static local:TouchValue.E2}" Fill="White" />
<Polygon Canvas.Left="900" Canvas.Top="511" Points="117,209 195,132 140,0 30,0 0,30 0,139 65,209" Tag="{x:Static local:TouchValue.B2}" Fill="White" />
<Polygon Canvas.Left="1020" Canvas.Top="607" Points="0,113 113,0 226,113 113,226" Tag="{x:Static local:TouchValue.E3}" Fill="White" />
<Polygon Canvas.Left="900" Canvas.Top="721" Points="120,0 198,78 140,208 30,208 0,180 0,71 65,0" Tag="{x:Static local:TouchValue.B3}" Fill="White" />
<Polygon Canvas.Left="930" Canvas.Top="930" Points="0,0 0,160 160,160, 160,0 0,0" Tag="{x:Static local:TouchValue.E4}" Fill="White" />
<Polygon Canvas.Left="721" Canvas.Top="901" Points="0,112 87,198 208,140 208,29 177,0 71,0 0,65" Tag="{x:Static local:TouchValue.B4}" Fill="White" />
<Polygon Canvas.Left="607" Canvas.Top="1013" Points="0,113 113,0 226,113 113,226" Tag="{x:Static local:TouchValue.E5}" Fill="White" />
<Polygon Canvas.Left="512" Canvas.Top="901" Points="208,112 121,198 0,140 0,29 31,0 137,0 208,65" Tag="{x:Static local:TouchValue.B5}" Fill="White" />
<Polygon Canvas.Left="350" Canvas.Top="930" Points="0,0 0,160 160,160, 160,0 0,0" Tag="{x:Static local:TouchValue.E6}" Fill="White" />
<Polygon Canvas.Left="349" Canvas.Top="721" Points="78,0 0,78 58,208 163,208 193,180 193,71 133,0" Tag="{x:Static local:TouchValue.B6}" Fill="White" />
<Polygon Canvas.Left="200" Canvas.Top="607" Points="0,113 113,0 226,113 113,226" Tag="{x:Static local:TouchValue.E7}" Fill="White" />
<Polygon Canvas.Left="345" Canvas.Top="511" Points="82,209 0,127 55,0 165,0 195,30 195,139 137,209" Tag="{x:Static local:TouchValue.B7}" Fill="White" />
<Polygon Canvas.Left="350" Canvas.Top="350" Points="0,0 0,160 160,160, 160,0 0,0" Tag="{x:Static local:TouchValue.E8}" Fill="White" />
<Polygon Canvas.Left="511" Canvas.Top="346" Points="209,78 131,0 0,55 0,165 29,195 139,195 209,130" Tag="{x:Static local:TouchValue.B8}" Fill="White" />
<Polygon Canvas.Left="720" Canvas.Top="583" Points="0,0 60,0 140,80 140,200 60,280 0,280 0,0" Tag="{x:Static local:TouchValue.C1}" Fill="White" />
<Polygon Canvas.Left="579" Canvas.Top="583" Points="141,280 81,280 0,199 1,81 81,0 141,0 141,280" Tag="{x:Static local:TouchValue.C2}" Fill="White" />
<Border x:Name="ResizeGrip" Width="150" Height="90"
Style="{StaticResource ResizeGripStyle}"
MouseDown="ResizeGrip_MouseDown"
Canvas.Left="1290" Canvas.Top="1350"
HorizontalAlignment="Center" VerticalAlignment="Top">
<TextBlock FontSize="50" Text="{Binding LbTouchPanelResize}"
VerticalAlignment="Center" HorizontalAlignment="Center" d:Text="Resize"/>
</Border>
</Canvas>
</Border>
<Polygon Canvas.Left="699" Canvas.Top="6" Points="0,0 39,0 111,5 113,6 111,24 80,243 77,243 63,229 56,221 23,188 16,194 -34,244 -36,244 -70,4 -70,3 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.D1}" Fill="White" />
<Polygon Canvas.Left="825" Canvas.Top="13" Points="0,0 10,1 95,19 145,37 179,50 207,64 230,75 254,87 297,116 318,130 316,135 300,156 286,175 270,196 256,215 240,236 226,255 210,276 197,294 181,315 168,333 101,333 66,318 21,299 9,293 -35,249 -34,237 -1,4 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.A1}" Fill="White" />
<Polygon Canvas.Left="1156" Canvas.Top="154" Points="0,0 8,6 21,18 32,27 45,39 56,48 69,60 77,67 86,77 93,85 105,99 116,111 125,122 130,128 118,138 102,150 85,163 68,176 47,192 30,205 13,218 -8,234 -25,247 -42,260 -62,275 -63,275 -63,255 -62,207 -61,193 -143,193 -141,189 -127,170 -111,149 -97,130 -81,109 -68,91 -52,70 -38,51 -22,30 -8,11 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.D2}" Fill="White" />
<Polygon Canvas.Left="1295" Canvas.Top="294" Points="0,0 4,5 21,31 34,51 44,69 89,159 106,213 123,268 132,318 132,320 -116,354 -120,352 -164,308 -179,271 -195,232 -201,217 -201,151 -190,142 -171,128 -150,112 -131,98 -110,82 -91,68 -70,52 -51,38 -30,22 -11,8 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.A2}" Fill="White" />
<Polygon Canvas.Left="1427" Canvas.Top="628" Points="0,0 3,0 5,30 8,90 7,116 4,174 3,183 -15,181 -235,150 -235,147 -216,128 -208,121 -182,95 -179,92 -187,85 -237,35 -237,34 -13,2 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.D3}" Fill="White" />
<Polygon Canvas.Left="1176" Canvas.Top="791" Points="0,0 13,1 247,33 251,34 250,43 238,100 223,144 208,187 195,224 182,248 169,271 159,290 146,313 136,329 121,351 119,352 101,339 80,323 61,309 40,293 21,279 0,263 -19,249 -40,233 -59,219 -80,203 -82,202 -82,136 -67,99 -45,44 -38,37 -30,30" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.A3}" Fill="White" />
<Polygon Canvas.Left="1094" Canvas.Top="1010" Points="0,0 4,2 20,14 33,24 54,40 75,56 95,71 113,85 129,97 146,110 167,126 188,142 193,146 191,150 179,163 168,175 159,185 147,198 136,210 132,215 124,222 111,234 99,245 87,256 77,265 67,274 63,272 51,256 41,243 28,226 18,213 5,196 -8,179 -18,166 -31,149 -41,136 -54,119 -67,102 -77,89 -81,83 1,83" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.D4}" Fill="White" />
<Polygon Canvas.Left="927" Canvas.Top="1093" Points="0,0 67,0 81,18 95,37 111,58 126,78 142,99 154,115 170,136 184,155 194,168 210,189 219,201 214,205 189,221 172,232 150,246 136,254 108,268 85,279 72,285 10,307 -7,313 -98,333 -101,333 -103,324 -135,88 -135,82 -128,75 -121,67 -92,38 -56,23 -15,6" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.A4}" Fill="White" />
<Polygon Canvas.Left="664" Canvas.Top="1190" Points="0,0 7,6 18,17 26,24 44,42 52,49 58,54 90,22 98,15 113,0 115,1 148,234 148,238 146,239 66,244 54,244 -35,238 -10,63 -1,1" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.D5}" Fill="White" />
<Polygon Canvas.Left="448" Canvas.Top="1092" Points="0,0 66,0 112,19 156,37 164,44 202,82 197,121 168,331 167,334 153,332 106,323 1,288 -97,239 -135,214 -152,203 -151,199 -135,178 -120,158 -104,137 -88,116 -79,104 -63,83 -51,67 -35,46 -23,30 -7,9 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.A5}" Fill="White" />
<Polygon Canvas.Left="347" Canvas.Top="1013" Points="0,0 2,0 2,74 1,80 46,79 84,79 82,83 69,100 56,117 43,134 30,151 17,168 4,185 -9,202 -22,219 -35,236 -48,253 -61,270 -64,270 -77,258 -85,251 -99,239 -111,228 -122,219 -135,207 -144,196 -156,183 -165,172 -177,159 -186,148 -191,142 -172,128 -153,114 -132,98 -118,88 -97,72 -79,59 -58,43 -39,29 -20,15 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.D6}" Fill="White" />
<Polygon Canvas.Left="257" Canvas.Top="791" Points="0,0 9,0 52,43 65,73 83,116 91,135 91,202 73,216 54,230 36,244 17,258 -1,272 -20,286 -33,296 -54,312 -73,326 -91,340 -109,353 -111,353 -123,334 -135,316 -148,296 -182,228 -192,209 -201,190 -228,103 -233,85 -241,39 -241,34 -240,33 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.A6}" Fill="White" />
<Polygon Canvas.Left="13" Canvas.Top="627" Points="0,0 24,3 238,33 232,40 223,50 203,70 196,78 183,91 185,95 240,150 240,151 139,165 7,183 0,183 -7,89 -1,1 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.D7}" Fill="White" />
<Polygon Canvas.Left="145" Canvas.Top="295" Points="0,0 5,2 26,18 42,30 63,46 82,60 103,76 122,90 140,104 159,118 180,134 199,148 203,151 203,193 202,220 180,274 165,311 154,322 146,329 122,353 108,352 -130,320 -131,318 -123,274 -96,187 -87,157 -66,115 -55,92 -44,70 -30,48 -14,22 -2,3 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.A7}" Fill="White" />
<Polygon Canvas.Left="283" Canvas.Top="155" Points="0,0 5,5 17,20 29,36 42,53 58,74 74,95 90,116 106,137 119,154 135,175 146,190 146,191 65,191 66,256 66,275 58,269 46,260 29,247 12,234 -5,221 -22,208 -39,195 -56,182 -73,169 -90,156 -107,143 -124,130 -128,126 -119,117 -112,109 -101,97 -94,89 -82,76 -75,68 -64,56 -52,45 -41,36 -29,25 -18,16 -5,4 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.D8}" Fill="White" />
<Polygon Canvas.Left="613" Canvas.Top="13" Points="0,0 3,0 10,48 38,247 37,252 24,265 16,272 -8,296 -100,333 -165,333 -179,315 -192,298 -205,281 -218,264 -231,247 -244,230 -257,213 -270,196 -283,179 -296,162 -309,145 -317,134 -316,131 -300,121 -285,111 -265,98 -249,88 -165,46 -72,15 -9,2 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.A8}" Fill="White" />
</Viewbox>
</DockPanel>
<Polygon Canvas.Left="720" Canvas.Top="215" Points="0,0 4,2 48,46 56,53 97,94 91,101 2,190 -2,188 -10,181 -54,137 -62,130 -96,96 -94,92 -73,71 -65,64 -27,26 -19,19 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.E1}" Fill="White" />
<Polygon Canvas.Left="801" Canvas.Top="347" Points="0,0 25,10 61,25 107,44 129,53 130,54 129,88 126,158 112,171 104,178 91,190 88,192 59,192 -7,191 -12,188 -23,177 -30,169 -38,161 -45,153 -53,145 -60,137 -69,128 -73,123 -73,105 -72,71 -33,32 -25,25 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.B1}" Fill="White" />
<Polygon Canvas.Left="1070" Canvas.Top="361" Points="0,0 8,0 8,137 -127,137 -128,57 -128,1 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.E2}" Fill="White" />
<Polygon Canvas.Left="938" Canvas.Top="513" Points="0,0 103,0 113,23 130,64 150,112 156,126 154,130 141,142 112,171 104,178 83,199 31,199 -19,149 -26,141 -36,131 -37,57 -37,35 -30,28 -22,21 -11,10 -3,3 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.B2}" Fill="White" />
<Polygon Canvas.Left="1130" Canvas.Top="623" Points="0,0 4,2 45,43 53,50 97,94 91,101 2,190 -2,188 -10,181 -55,136 -63,129 -96,96 -94,92 -74,72 -66,65 -28,27 -20,20 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.E3}" Fill="White" />
<Polygon Canvas.Left="968" Canvas.Top="726" Points="0,0 20,0 53,1 109,57 117,64 126,73 123,82 105,125 87,168 74,199 73,200 -31,200 -67,164 -67,123 -66,68 -51,53 -44,45 -5,6 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.B3}" Fill="White" />
<Polygon Canvas.Left="1053" Canvas.Top="939" Points="0,0 25,0 25,137 -110,137 -111,57 -111,1 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.E4}" Fill="White" />
<Polygon Canvas.Left="794" Canvas.Top="900" Points="0,0 97,0 133,36 134,68 134,138 124,143 88,158 48,175 10,191 6,190 -3,181 -11,174 -65,120 -66,112 -67,68 -4,5 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.B4}" Fill="White" />
<Polygon Canvas.Left="720" Canvas.Top="1034" Points="0,0 4,2 97,95 91,102 2,191 -2,189 -35,156 -43,149 -87,105 -95,98 -97,95 -90,88 -82,81 -44,43 -36,36 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.E5}" Fill="White" />
<Polygon Canvas.Left="550" Canvas.Top="899" Points="0,0 97,1 143,47 150,55 163,68 164,121 92,193 83,190 37,171 -4,154 -37,140 -37,101 -36,37 -24,25 -17,17 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.B5}" Fill="White" />
<Polygon Canvas.Left="474" Canvas.Top="939" Points="0,0 25,0 25,137 -110,137 -111,53 -111,1 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.E6}" Fill="White" />
<Polygon Canvas.Left="420" Canvas.Top="727" Points="0,0 52,0 119,67 119,162 109,172 102,180 82,200 -20,200 -38,158 -50,129 -68,86 -73,73 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.B6}" Fill="White" />
<Polygon Canvas.Left="311" Canvas.Top="623" Points="0,0 4,2 61,59 69,66 97,94 91,101 2,190 -2,189 -51,140 -59,133 -96,96 -94,92 -71,69 -63,62 -25,24 -17,17 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.E7}" Fill="White" />
<Polygon Canvas.Left="401" Canvas.Top="512" Points="0,0 102,0 124,22 132,29 140,37 140,69 139,130 133,138 101,170 94,178 72,200 20,201 -53,128 -50,119 -31,74 -12,29 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.B7}" Fill="White" />
<Polygon Canvas.Left="491" Canvas.Top="361" Points="0,0 8,0 8,137 -127,137 -128,53 -128,1 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.E8}" Fill="White" />
<Polygon Canvas.Left="640" Canvas.Top="348" Points="0,0 8,6 74,72 73,124 45,152 38,160 17,181 10,189 8,191 -90,191 -127,154 -126,50 -88,35 -20,8 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.B8}" Fill="White" />
<Polygon Canvas.Left="728" Canvas.Top="583" Points="0,0 51,0 65,14 72,22 129,79 129,193 100,222 93,230 65,258 58,266 50,274 10,273 0,272 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.C1}" Fill="White" />
<Polygon Canvas.Left="662" Canvas.Top="583" Points="0,0 52,0 52,267 51,273 2,273 -78,193 -78,80 -47,49 -40,41 -5,6 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.C2}" Fill="White" />
<Polygon Canvas.Left="714" Canvas.Top="583" Points="0,0 14,0 14,273 0,273 0,0" PreviewTouchDown="Element_TouchDown" PreviewTouchMove="Element_TouchMove" PreviewTouchUp="Element_TouchUp" Tag="{x:Static local:TouchValue.C3}" Fill="White" />
<Border x:Name="ResizeGrip"
Width="150" Height="90" Background="White"
MouseDown="ResizeGrip_MouseDown" Canvas.Left="1290" Canvas.Top="1350" HorizontalAlignment="Center" VerticalAlignment="Top">
<Label FontSize="50" Content="Resize" />
</Border>
</Canvas>
</Viewbox>
</StackPanel>
</Grid>
</Border>

View File

@ -1,8 +1,12 @@
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using WpfMaiTouchEmulator.Managers;
namespace WpfMaiTouchEmulator;
/// <summary>
@ -12,11 +16,14 @@ public partial class TouchPanel : Window
{
internal Action<TouchValue>? onTouch;
internal Action<TouchValue>? onRelease;
internal Action? onInitialReposition;
private readonly Dictionary<int, Polygon> activeTouches = [];
private readonly Dictionary<int, (Polygon polygon, Point lastPoint)> activeTouches = new();
private readonly TouchPanelPositionManager _positionManager;
private List<Polygon> buttons = [];
private bool isDebugEnabled = Properties.Settings.Default.IsDebugEnabled;
private bool isRingButtonEmulationEnabled = Properties.Settings.Default.IsRingButtonEmulationEnabled;
private bool hasRepositioned = false;
private enum ResizeDirection
{
@ -30,35 +37,100 @@ public partial class TouchPanel : Window
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
public enum SizingEdge
{
Left = 1,
Right = 2,
Top = 3,
TopLeft = 4,
TopRight = 5,
Bottom = 6,
BottomLeft = 7,
BottomRight = 8
}
private const double FixedAspectRatio = 720.0 / 1280.0; // width / height
private const int MinWidth = 180;
private const int MinHeight = 320;
public TouchPanel()
{
InitializeComponent();
Topmost = true;
_positionManager = new TouchPanelPositionManager();
Loaded += Window_Loaded;
StateCheckLoop();
Touch.FrameReported += OnTouchFrameReported;
}
private async void StateCheckLoop()
protected override void OnSourceInitialized(EventArgs e)
{
while (true)
base.OnSourceInitialized(e);
var source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
source.AddHook(WndProc);
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
const int WM_SIZING = 0x0214;
if (msg == WM_SIZING)
{
if (activeTouches.Count != 0 && !TouchesOver.Any())
{
await Task.Delay(100);
if (activeTouches.Count != 0 && !TouchesOver.Any())
{
DeselectAllItems();
}
}
await Task.Delay(100);
var rect = Marshal.PtrToStructure<RECT>(lParam);
var edge = (SizingEdge)wParam.ToInt32();
EnforceAspectRatio(ref rect, edge);
Marshal.StructureToPtr(rect, lParam, true);
handled = true;
}
return IntPtr.Zero;
}
private void EnforceAspectRatio(ref RECT rect, SizingEdge edge)
{
var currentWidth = rect.Right - rect.Left;
var currentHeight = rect.Bottom - rect.Top;
int newWidth, newHeight;
if (edge == SizingEdge.BottomRight)
{
newWidth = (int)(currentHeight * FixedAspectRatio);
newHeight = currentHeight;
}
else
{
newHeight = (int)(currentWidth / FixedAspectRatio);
newWidth = currentWidth;
}
// Enforce minimum size while keeping the aspect ratio.
if (newWidth < MinWidth)
{
newWidth = MinWidth;
newHeight = (int)(newWidth / FixedAspectRatio);
}
if (newHeight < MinHeight)
{
newHeight = MinHeight;
newWidth = (int)(newHeight * FixedAspectRatio);
}
rect.Right = rect.Left + newWidth;
rect.Bottom = rect.Top + newHeight;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
buttons = VisualTreeHelperExtensions.FindVisualChildren<Polygon>(this);
DeselectAllItems();
}
public void PositionTouchPanel()
@ -73,6 +145,12 @@ public partial class TouchPanel : Window
Left = position.Value.Left;
Width = position.Value.Width;
Height = position.Value.Height;
if (!hasRepositioned)
{
hasRepositioned = true;
onInitialReposition?.Invoke();
}
}
}
@ -86,74 +164,111 @@ public partial class TouchPanel : Window
{
if (e.LeftButton == MouseButtonState.Pressed)
{
ResizeWindow(ResizeDirection.BottomRight);
ResizeWindow(SizingEdge.BottomRight);
}
}
private void ResizeWindow(ResizeDirection direction)
private void ResizeWindow(SizingEdge edge)
{
ReleaseCapture();
SendMessage(new System.Windows.Interop.WindowInteropHelper(this).Handle,
0x112, // WM_SYSCOMMAND message
(IntPtr)(0xF000 + direction),
IntPtr.Zero);
SendMessage(new WindowInteropHelper(this).Handle, 0x112, (IntPtr)(0xF000 + (int)edge), IntPtr.Zero);
}
private void Element_TouchDown(object sender, TouchEventArgs e)
private void OnTouchFrameReported(object sender, TouchFrameEventArgs e)
{
// Cast the sender to a Border to ensure it's the correct element type.
if (sender is Polygon element)
{
// Highlight the element and add it to the active touches tracking.
HighlightElement(element, true);
onTouch?.Invoke((TouchValue)element.Tag);
activeTouches[e.TouchDevice.Id] = element;
}
e.Handled = true;
}
var currentTouchPoints = e.GetTouchPoints(this);
var currentIds = new HashSet<int>();
private void Element_TouchMove(object sender, TouchEventArgs e)
{
// Attempt to find the element under the current touch point.
var touchPoint = e.GetTouchPoint(this).Position;
var hitTestResult = VisualTreeHelper.HitTest(this, touchPoint);
if (hitTestResult != null && hitTestResult.VisualHit is Polygon newElement)
foreach (var touch in currentTouchPoints)
{
// If this touch point is already tracking another element, unhighlight the previous one.
if (activeTouches.TryGetValue(e.TouchDevice.Id, out var previousElement) && previousElement != newElement)
var id = touch.TouchDevice.Id;
// If the touch is released, process it as a TouchUp.
if (touch.Action == TouchAction.Up)
{
Task.Delay(50)
.ContinueWith(t =>
if (activeTouches.TryGetValue(id, out var touchInfo2))
{
if (activeTouches.Values.Count(v => v.polygon == touchInfo2.polygon) == 1)
{
HighlightElement(previousElement, false);
Application.Current.Dispatcher.Invoke(() =>
HighlightElement(touchInfo2.polygon, false);
onRelease?.Invoke((TouchValue)touchInfo2.polygon.Tag);
if (isRingButtonEmulationEnabled)
{
onRelease?.Invoke((TouchValue)previousElement.Tag);
});
});
RingButtonEmulator.ReleaseButton((TouchValue)touchInfo2.polygon.Tag);
}
}
activeTouches.Remove(id);
}
continue;
}
// Highlight the new element and update the tracking.
HighlightElement(newElement, true);
onTouch?.Invoke((TouchValue)newElement.Tag);
activeTouches[e.TouchDevice.Id] = newElement;
currentIds.Add(id);
// New touch (TouchDown)
if (!activeTouches.TryGetValue(id, out var touchInfo))
{
if (VisualTreeHelper.HitTest(this, touch.Position)?.VisualHit is Polygon polygon)
{
HighlightElement(polygon, true);
activeTouches[id] = (polygon, touch.Position);
onTouch?.Invoke((TouchValue)polygon.Tag);
if (isRingButtonEmulationEnabled && RingButtonEmulator.HasRingButtonMapping((TouchValue)polygon.Tag))
{
RingButtonEmulator.PressButton((TouchValue)polygon.Tag);
}
}
}
// Existing touch (TouchMove)
else
{
var previousPosition = touchInfo.lastPoint;
var currentPosition = touch.Position;
var sampleCount = 10;
var changed = false;
for (var i = 1; i <= sampleCount; i++)
{
var t = (double)i / sampleCount;
var samplePoint = new Point(
previousPosition.X + (currentPosition.X - previousPosition.X) * t,
previousPosition.Y + (currentPosition.Y - previousPosition.Y) * t);
if (VisualTreeHelper.HitTest(this, samplePoint)?.VisualHit is Polygon polygon && polygon != touchInfo.polygon)
{
if (activeTouches.Values.Count(v => v.polygon == touchInfo.polygon) == 1)
{
HighlightElement(touchInfo.polygon, false);
onRelease?.Invoke((TouchValue)touchInfo.polygon.Tag);
}
HighlightElement(polygon, true);
onTouch?.Invoke((TouchValue)polygon.Tag);
activeTouches[id] = (polygon, samplePoint);
changed = true;
break;
}
}
if (!changed)
{
activeTouches[id] = (touchInfo.polygon, currentPosition);
}
}
}
e.Handled = true;
}
private void Element_TouchUp(object sender, TouchEventArgs e)
{
// When touch is lifted, unhighlight the associated element and remove it from tracking.
if (activeTouches.TryGetValue(e.TouchDevice.Id, out var element))
// Process any touches that might not be reported this frame.
var endedTouches = activeTouches.Keys.Except(currentIds).ToList();
foreach (var id in endedTouches)
{
HighlightElement(element, false);
onRelease?.Invoke((TouchValue)element.Tag);
activeTouches.Remove(e.TouchDevice.Id);
var touchInfo = activeTouches[id];
if (activeTouches.Values.Count(v => v.polygon == touchInfo.polygon) == 1)
{
HighlightElement(touchInfo.polygon, false);
onRelease?.Invoke((TouchValue)touchInfo.polygon.Tag);
if (isRingButtonEmulationEnabled)
{
RingButtonEmulator.ReleaseButton((TouchValue)touchInfo.polygon.Tag);
}
}
activeTouches.Remove(id);
}
e.Handled = true;
}
private void DeselectAllItems()
@ -161,10 +276,11 @@ public partial class TouchPanel : Window
// Logic to deselect all items or the last touched item
foreach (var element in activeTouches.Values)
{
HighlightElement(element, false);
onRelease?.Invoke((TouchValue)element.Tag);
HighlightElement(element.polygon, false);
onRelease?.Invoke((TouchValue)element.polygon.Tag);
}
activeTouches.Clear();
RingButtonEmulator.ReleaseAllButtons();
}
public void SetDebugMode(bool enabled)
@ -176,6 +292,434 @@ public partial class TouchPanel : Window
});
}
public void SetLargeButtonMode(bool enabled)
{
TouchValue[] ringButtonsValues = {
TouchValue.A1,
TouchValue.A2,
TouchValue.A3,
TouchValue.A4,
TouchValue.A5,
TouchValue.A6,
TouchValue.A7,
TouchValue.A8,
TouchValue.D1,
TouchValue.D2,
TouchValue.D3,
TouchValue.D4,
TouchValue.D5,
TouchValue.D6,
TouchValue.D7,
TouchValue.D8,
};
var a1 = buttons.First(button => (TouchValue)button.Tag == TouchValue.A1);
var a2 = buttons.First(button => (TouchValue)button.Tag == TouchValue.A2);
var a3 = buttons.First(button => (TouchValue)button.Tag == TouchValue.A3);
var a4 = buttons.First(button => (TouchValue)button.Tag == TouchValue.A4);
var a5 = buttons.First(button => (TouchValue)button.Tag == TouchValue.A5);
var a6 = buttons.First(button => (TouchValue)button.Tag == TouchValue.A6);
var a7 = buttons.First(button => (TouchValue)button.Tag == TouchValue.A7);
var a8 = buttons.First(button => (TouchValue)button.Tag == TouchValue.A8);
var d1 = buttons.First(button => (TouchValue)button.Tag == TouchValue.D1);
var d2 = buttons.First(button => (TouchValue)button.Tag == TouchValue.D2);
var d3 = buttons.First(button => (TouchValue)button.Tag == TouchValue.D3);
var d4 = buttons.First(button => (TouchValue)button.Tag == TouchValue.D4);
var d5 = buttons.First(button => (TouchValue)button.Tag == TouchValue.D5);
var d6 = buttons.First(button => (TouchValue)button.Tag == TouchValue.D6);
var d7 = buttons.First(button => (TouchValue)button.Tag == TouchValue.D7);
var d8 = buttons.First(button => (TouchValue)button.Tag == TouchValue.D8);
if (enabled)
{
d1.Points = new PointCollection
{
new Point(-5, -50),
new Point(205, -50),
new Point(165, 253),
new Point(100, 188),
new Point(35, 253),
};
a1.Points = new PointCollection
{
new Point(495, -50),
new Point(208, 338),
new Point(145, 338),
new Point(49, 297),
new Point(0, 249),
new Point(42, -55),
};
d2.Points = new PointCollection
{
new Point(290, -182),
new Point(500, -180),
new Point(500, -5),
new Point(96, 297),
new Point(96, 205),
new Point(0, 205),
};
a2.Points = new PointCollection
{
new Point(405, 317),
new Point(91, 362),
new Point(42, 314),
new Point(0, 219),
new Point(0, 150),
new Point(405, -150),
};
d3.Points = new PointCollection
{
new Point(315, -10),
new Point(315, 208),
new Point(0, 165),
new Point(65, 100),
new Point(0, 35),
};
a3.Points = new PointCollection
{
new Point(406, 520),
new Point(0, 213),
new Point(0, 144),
new Point(41, 48),
new Point(89, 0),
new Point(406, 43),
};
d4.Points = new PointCollection
{
new Point(500, 309),
new Point(500, 491),
new Point(305, 491),
new Point(0, 92),
new Point(92, 92),
new Point(92, 0),
};
a4.Points = new PointCollection
{
new Point(45, 400),
new Point(0, 83),
new Point(48, 35),
new Point(144, 0),
new Point(212, 0),
new Point(515, 400),
};
d5.Points = new PointCollection
{
new Point(208, 317),
new Point(-10, 317),
new Point(34, 0),
new Point(99, 65),
new Point(164, 0),
};
a5.Points = new PointCollection
{
new Point(317, 400),
new Point(363, 83),
new Point(316, 35),
new Point(220, 0),
new Point(152, 0),
new Point(-150, 400),
};
d6.Points = new PointCollection
{
new Point(-10, 492),
new Point(-200, 492),
new Point(-200, 295),
new Point(199, 0),
new Point(199, 92),
new Point(291, 92),
};
a6.Points = new PointCollection
{
new Point(-67, 505),
new Point(333, 214),
new Point(333, 144),
new Point(296, 48),
new Point(248, 0),
new Point(-67, 45),
};
d7.Points = new PointCollection
{
new Point(-60, 207),
new Point(-60, -7),
new Point(253, 34),
new Point(188, 99),
new Point(253, 164),
};
a7.Points = new PointCollection
{
new Point(-65, 320),
new Point(248, 362),
new Point(297, 314),
new Point(333, 219),
new Point(333, 151),
new Point(-65, -150),
};
d8.Points = new PointCollection
{
new Point(-195, -10),
new Point(-195, -195),
new Point(-5, -195),
new Point(298, 199),
new Point(200, 199),
new Point(200, 291),
};
a8.Points = new PointCollection
{
new Point(-148, -55),
new Point(153, 338),
new Point(215, 338),
new Point(311, 297),
new Point(359, 249),
new Point(318, -55),
};
}
else
{
d1.Points = new PointCollection
{
new Point(0, 5),
new Point(50, 2),
new Point(100, 0),
new Point(150, 2),
new Point(200, 5),
new Point(165, 253),
new Point(100, 188),
new Point(35, 253),
};
a1.Points = new PointCollection
{
new Point(150, 28),
new Point(245, 65),
new Point(360, 133),
new Point(208, 338),
new Point(145, 338),
new Point(49, 297),
new Point(0, 249),
new Point(35, 0),
};
d2.Points = new PointCollection
{
new Point(153, 0),
new Point(187, 32),
new Point(225, 67),
new Point(259, 104),
new Point(295, 147),
new Point(96, 297),
new Point(96, 205),
new Point(0, 205),
};
a2.Points = new PointCollection
{
new Point(261, 101),
new Point(303, 195),
new Point(339, 327),
new Point(91, 362),
new Point(42, 314),
new Point(0, 219),
new Point(0, 150),
new Point(202, 0),
};
d3.Points = new PointCollection
{
new Point(248, 0),
new Point(251, 48),
new Point(253, 100),
new Point(251, 150),
new Point(247, 199),
new Point(0, 165),
new Point(65, 100),
new Point(0, 35),
};
a3.Points = new PointCollection
{
new Point(305, 150),
new Point(269, 246),
new Point(201, 364),
new Point(0, 213),
new Point(0, 144),
new Point(41, 48),
new Point(89, 0),
new Point(337, 34),
};
d4.Points = new PointCollection
{
new Point(292, 151),
new Point(260, 187),
new Point(225, 225),
new Point(188, 259),
new Point(151, 291),
new Point(0, 92),
new Point(92, 92),
new Point(92, 0),
};
a4.Points = new PointCollection
{
new Point(260, 259),
new Point(167, 301),
new Point(37, 335),
new Point(0, 83),
new Point(48, 35),
new Point(144, 0),
new Point(212, 0),
new Point(364, 200),
};
d5.Points = new PointCollection
{
new Point(199, 252),
new Point(151, 255),
new Point(99, 257),
new Point(49, 255),
new Point(0, 252),
new Point(34, 0),
new Point(99, 65),
new Point(164, 0),
};
a5.Points = new PointCollection
{
new Point(104, 259),
new Point(197, 301),
new Point(327, 335),
new Point(363, 83),
new Point(316, 35),
new Point(220, 0),
new Point(152, 0),
new Point(0, 201),
};
d6.Points = new PointCollection
{
new Point(140, 292),
new Point(104, 260),
new Point(66, 225),
new Point(32, 188),
new Point(0, 151),
new Point(199, 0),
new Point(199, 92),
new Point(291, 92),
};
a6.Points = new PointCollection
{
new Point(32, 150),
new Point(68, 246),
new Point(133, 365),
new Point(333, 214),
new Point(333, 144),
new Point(296, 48),
new Point(248, 0),
new Point(0, 35),
};
d7.Points = new PointCollection
{
new Point(5, 199),
new Point(2, 151),
new Point(0, 99),
new Point(2, 49),
new Point(6, 0),
new Point(253, 34),
new Point(188, 99),
new Point(253, 164),
};
a7.Points = new PointCollection
{
new Point(78, 101),
new Point(36, 195),
new Point(0, 327),
new Point(248, 362),
new Point(297, 314),
new Point(333, 219),
new Point(333, 151),
new Point(132, 0),
};
d8.Points = new PointCollection
{
new Point(0, 140),
new Point(32, 104),
new Point(67, 66),
new Point(104, 32),
new Point(145, 0),
new Point(298, 199),
new Point(200, 199),
new Point(200, 291),
};
a8.Points = new PointCollection
{
new Point(210, 28),
new Point(115, 65),
new Point(0, 138),
new Point(153, 338),
new Point(215, 338),
new Point(311, 297),
new Point(359, 249),
new Point(324, 0),
};
}
}
public void SetBorderMode(BorderSetting borderSetting, string borderColour)
{
if (borderSetting == BorderSetting.Rainbow)
{
var rotateTransform = new RotateTransform { CenterX = 0.5, CenterY = 0.5 };
touchPanelBorder.BorderBrush = new ImageBrush {
ImageSource = new BitmapImage(new Uri(@"pack://application:,,,/Assets/conicalGradient.png")),
ViewportUnits = BrushMappingMode.RelativeToBoundingBox,
Viewport = new Rect(0, 0, 1, 1),
TileMode = TileMode.Tile,
RelativeTransform = rotateTransform,
};
var animation = new DoubleAnimation
{
From = 0,
To = 360,
Duration = new Duration(TimeSpan.FromSeconds(10)),
RepeatBehavior = RepeatBehavior.Forever
};
rotateTransform.BeginAnimation(RotateTransform.AngleProperty, animation);
return;
}
else if (borderSetting == BorderSetting.Solid)
{
try
{
var colour = (Color)ColorConverter.ConvertFromString(borderColour);
touchPanelBorder.BorderBrush = new SolidColorBrush { Color = colour };
return;
}
catch (Exception ex)
{
Logger.Error("Failed to parse solid colour", ex);
}
}
touchPanelBorder.BorderBrush = null;
}
public void SetEmulateRingButton(bool enabled)
{
isRingButtonEmulationEnabled = enabled;
}
private void HighlightElement(Polygon element, bool highlight)
{
if (isDebugEnabled)

View File

@ -2,160 +2,31 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<TargetFramework>net8.0-windows10.0.17763.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<ApplicationIcon>Assets\icon.ico</ApplicationIcon>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<None Remove="Assets\Images\A1.png" />
<None Remove="Assets\Images\A2.png" />
<None Remove="Assets\Images\A3.png" />
<None Remove="Assets\Images\A4.png" />
<None Remove="Assets\Images\A5.png" />
<None Remove="Assets\Images\A6.png" />
<None Remove="Assets\Images\A7.png" />
<None Remove="Assets\Images\A8.png" />
<None Remove="Assets\Images\B1.png" />
<None Remove="Assets\Images\B2.png" />
<None Remove="Assets\Images\B3.png" />
<None Remove="Assets\Images\B4.png" />
<None Remove="Assets\Images\B5.png" />
<None Remove="Assets\Images\B6.png" />
<None Remove="Assets\Images\B7.png" />
<None Remove="Assets\Images\B8.png" />
<None Remove="Assets\Images\C1+C2.png" />
<None Remove="Assets\Images\C1.png" />
<None Remove="Assets\Images\C2.png" />
<None Remove="Assets\Images\D1.png" />
<None Remove="Assets\Images\D2.png" />
<None Remove="Assets\Images\D3.png" />
<None Remove="Assets\Images\D4.png" />
<None Remove="Assets\Images\D5.png" />
<None Remove="Assets\Images\D6.png" />
<None Remove="Assets\Images\D7.png" />
<None Remove="Assets\Images\D8.png" />
<None Remove="Assets\Images\E1.png" />
<None Remove="Assets\Images\E2.png" />
<None Remove="Assets\Images\E3.png" />
<None Remove="Assets\Images\E4.png" />
<None Remove="Assets\Images\E5.png" />
<None Remove="Assets\Images\E6.png" />
<None Remove="Assets\Images\E7.png" />
<None Remove="Assets\Images\E8.png" />
<None Remove="Assets\conicalGradient.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Drawing.Common" Version="8.0.1" />
<PackageReference Include="System.IO.Ports" Version="8.0.0" />
<Content Include="Assets\icon.ico" />
</ItemGroup>
<ItemGroup>
<Resource Include="Assets\Images\A1.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\A2.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\A3.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\A4.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\A5.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\A6.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\A7.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\A8.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\B1.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\B2.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\B3.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\B4.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\B5.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\B6.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\B7.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\B8.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\C1+C2.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\C1.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\C2.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\D1.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\D2.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\D3.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\D4.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\D5.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\D6.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\D7.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\D8.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\E1.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\E2.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\E3.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\E4.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\E5.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\E6.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\E7.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Assets\Images\E8.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<PackageReference Include="System.Drawing.Common" Version="9.0.0" />
<PackageReference Include="System.IO.Ports" Version="9.0.0" />
<PackageReference Include="WPF-UI" Version="3.0.5" />
</ItemGroup>
<ItemGroup>
<Resource Include="Assets\conicalGradient.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Resource>
</ItemGroup>
@ -165,6 +36,18 @@
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
<Compile Update="Resources\Strings.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Strings.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\Strings.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Strings.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>