Compare commits

..

37 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
36 changed files with 1954 additions and 335 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

@ -25,6 +25,21 @@
<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.

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,52 +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="360" Width="500" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
<StackPanel VerticalAlignment="Top">
<Menu Width="Auto" Height="20">
<MenuItem Header="{Binding LbMenuCategoryHelp}" d:Header="_Help">
<MenuItem x:Name="instructionsLabel" Header="{Binding LbMenuItemSetup}" HorizontalAlignment="Left" Width="220" Click="instructionsLabel_Click"/>
</MenuItem>
</Menu>
<Grid Background="Transparent" Height="317" VerticalAlignment="Top" >
<Button x:Name="ConnectToPortButton" Content="{Binding LbConnectToPort}" d:Content="Connect to port" HorizontalAlignment="Left" Margin="10,95,0,0" VerticalAlignment="Top" Click="ConnectToPortButton_Click" Width="130" />
<TextBox x:Name="RecievedLogBox"
IsReadOnly="True"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
AcceptsReturn="True"
AcceptsTab="True"
TextWrapping="Wrap" Margin="376,34,0,219" HorizontalAlignment="Left" Width="88" />
<TextBox x:Name="SentLogBox"
IsReadOnly="True"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
AcceptsReturn="True"
AcceptsTab="True"
TextWrapping="Wrap" Margin="376,121,0,129" HorizontalAlignment="Left" Width="88" />
<Label x:Name="label" Content="{Binding LbRecievedData}" d:Content="Recieved Data" HorizontalAlignment="Left" Margin="376,8,0,0" VerticalAlignment="Top" Width="88"/>
<Label x:Name="label_Copy" Content="{Binding LbSentData}" d:Content="Sent Data" HorizontalAlignment="Left" Margin="376,95,0,0" VerticalAlignment="Top" Width="88"/>
<Label x:Name="label_Copy1" Content="{Binding LbButtonState}" d:Content="Button state" HorizontalAlignment="Left" Margin="238,56,0,0" VerticalAlignment="Top" Width="88"/>
<Label x:Name="buttonStateValue" Content="0" HorizontalAlignment="Left" Margin="238,82,0,0" VerticalAlignment="Top" RenderTransformOrigin="3.566,1.468" FontSize="10"/>
<CheckBox x:Name="debugMode" Content="{Binding LbDebugMode}" d:Content="Debug mode" HorizontalAlignment="Left" Margin="10,213,0,0" VerticalAlignment="Top" IsChecked="{Binding Path=IsDebugEnabled}" Click="debugMode_Click" Cursor="" Focusable="False" ToolTip="Show the touchpad with translucency and show inputs highlighted" Width="224"/>
<CheckBox x:Name="automaticTouchPanelPositioning" d:Content="Automatic touch panel positioning" HorizontalAlignment="Left" Margin="10,233,0,0" VerticalAlignment="Top" Height="17" Width="224" Content="{Binding LbAutoSensorPositioning}" IsChecked="{Binding Path=IsAutomaticPositioningEnabled}" Click="automaticTouchPanelPositioning_Click" ToolTip="{Binding LbAutoSensorPositioningTT}"/>
<CheckBox x:Name="automaticPortConnecting" d:Content="Automatic port connecting" HorizontalAlignment="Left" Margin="10,255,0,0" VerticalAlignment="Top" Height="17" Width="224" Content="{Binding LbAutoPortConnecting}" IsChecked="{Binding Path=IsAutomaticPortConnectingEnabled}" Click="automaticPortConnecting_Click" ToolTip="{Binding LbAutoPortConnectingTT}"/>
<Label x:Name="label1" Content="{Binding LbConnectionState}" d:Content="Connection state" HorizontalAlignment="Left" Margin="238,8,0,0" VerticalAlignment="Top"/>
<Label x:Name="connectionStateLabel" Content="{Binding LbConnectionStateNotConnected}" d:Content="Not connected" HorizontalAlignment="Left" Margin="238,31,0,0" VerticalAlignment="Top" FontSize="10"/>
<CheckBox x:Name="exitWithSinmai" Content="{Binding LbExitWithSinmai}" d:Content="Exit with Sinmai" HorizontalAlignment="Left" Margin="10,277,0,0" VerticalAlignment="Top" Height="17" Width="224" IsChecked="{Binding IsExitWithSinmaiEnabled}" Click="exitWithSinmai_Click" ToolTip="{Binding LbExitWithSinmaiTT}"/>
<Button x:Name="buttonInstallComPort" Content="{Binding LbInstallComPort}" d:Content="Install COM port" HorizontalAlignment="Left" Margin="10,9,0,0" VerticalAlignment="Top" Click="buttonInstallComPort_Click" Width="130"/>
<Button x:Name="buttonUninstallComPorts" Content="{Binding LbUninstallComPort}" d:Content="Uninstall COM port" HorizontalAlignment="Left" Margin="10,34,0,0" VerticalAlignment="Top" Click="buttonUninstallComPorts_Click" Width="130"/>
<Button x:Name="buttonListComPorts" Content="{Binding LbListComPorts}" d:Content="List installed COM ports" HorizontalAlignment="Left" Margin="10,59,0,0" VerticalAlignment="Top" Click="buttonListComPorts_Click" Width="130"/>
<Separator HorizontalAlignment="Left" Margin="10,84,0,0" VerticalAlignment="Top" Height="6" Width="126"/>
<ComboBox x:Name="languageSelector" HorizontalAlignment="Left" Margin="10,186,0,0" VerticalAlignment="Top" Width="149" ItemsSource="{Binding SupportedLanguages}"
DisplayMemberPath="NativeName"
SelectedItem="{Binding SelectedLanguage, Mode=TwoWay}"/>
<Label x:Name="lagnuageLabel" Content="{Binding LbLanguageDropdown}" d:Content="Language" HorizontalAlignment="Left" Margin="10,155,0,0" VerticalAlignment="Top" Width="126"/>
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>
</Grid>
</StackPanel>
<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>
</Window>

View File

@ -1,26 +1,42 @@
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 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, (MainWindowViewModel)DataContext);
@ -63,17 +79,26 @@ public partial class MainWindow : Window
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.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.Owner = this;
var dataContext = (MainWindowViewModel)DataContext;
_touchPanel.DataContext = dataContext;
_touchPanel.SetDebugMode(dataContext.IsDebugEnabled);
_touchPanel.SetLargeButtonMode(dataContext.IsLargeButtonsEnabled);
if (Properties.Settings.Default.IsAutomaticPositioningEnabled)
{
_touchPanel.DragWindowHandle.Visibility = Visibility.Hidden;
@ -89,12 +114,10 @@ public partial class MainWindow : Window
{
e.Cancel = true;
await connector.Disconnect();
foreach (Window childWindow in OwnedWindows)
{
childWindow.Close();
}
_touchPanel?.Close();
Closing -= MainWindow_Closing;
e.Cancel = false;
Application.Current.Shutdown();
}
private async void ExitWithSinmaiLoop()
@ -140,6 +163,18 @@ public partial class MainWindow : Window
}
}
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;
@ -153,7 +188,7 @@ public partial class MainWindow : Window
{
if (dataContext.IsAutomaticPositioningEnabled)
{
_touchPanel.PositionTouchPanel();
_touchPanel?.PositionTouchPanel();
}
await Task.Delay(1000);
@ -167,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)
@ -186,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)
@ -241,4 +286,68 @@ public partial class MainWindow : Window
{
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

@ -8,64 +8,112 @@ namespace WpfMaiTouchEmulator;
public class MainWindowViewModel : INotifyPropertyChanged
{
// Language
public string LbAutoPortConnecting
public string? LbAutoPortConnecting
{
get; set;
}
public string LbAutoSensorPositioning
public string? LbAutoSensorPositioning
{
get; set;
}
public string LbButtonState
public string? LbButtonState
{
get; set;
}
public string LbConnectionState
public string? LbConnectionState
{
get; set;
}
public string LbConnectionStateNotConnected
public string? LbConnectionStateNotConnected
{
get; set;
}
public string LbConnectToPort
public string? LbConnectToPort
{
get; set;
}
public string LbDebugMode
public string? LbDebugMode
{
get; set;
}
public string LbExitWithSinmai
public string? LbLargeButtons
{
get; set;
}
public string LbInstallComPort
public string? LbLargeButtonsTT
{
get; set;
}
public string LbLanguageDropdown
public string? LbExitWithSinmai
{
get; set;
}
public string LbListComPorts
public string? LbEmulateRingButtons
{
get; set;
}
public string LbReceivedData
public string? LbOpenLogFolder
{
get; set;
}
public string LbRecievedData
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
public string? LbSentData
{
get; set;
}
public string LbUninstallComPort
public string? LbUninstallComPort
{
get; set;
}
@ -164,6 +212,11 @@ public class MainWindowViewModel : INotifyPropertyChanged
get;
private set;
}
public string? LbDebugModeTT
{
get;
private set;
}
public string? LbAutoSensorPositioningTT
{
get;
@ -174,12 +227,20 @@ public class MainWindowViewModel : INotifyPropertyChanged
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;
@ -218,6 +279,16 @@ public class MainWindowViewModel : INotifyPropertyChanged
}
}
public bool IsLargeButtonsEnabled
{
get => _isLargeButtonsEnabled;
set
{
_isLargeButtonsEnabled = value;
OnPropertyChanged();
}
}
public bool IsAutomaticPositioningEnabled
{
get => _isAutomaticPositioningEnabled;
@ -248,6 +319,27 @@ public 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;
@ -281,9 +373,23 @@ public class MainWindowViewModel : INotifyPropertyChanged
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");
@ -292,8 +398,10 @@ public class MainWindowViewModel : INotifyPropertyChanged
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");

View File

@ -1,11 +1,11 @@
using System.IO.Ports;
using System.Windows;
namespace WpfMaiTouchEmulator;
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;
@ -34,12 +34,12 @@ 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(_viewModel.TxtComPortConnecting);
@ -86,10 +86,10 @@ internal class MaiTouchComConnector(MaiTouchSensorButtonStateManager buttonState
{
while (!token.IsCancellationRequested)
{
if (isActiveMode)
if (_isActiveMode)
{
SendTouchscreenState();
Thread.Sleep(1);
Thread.Sleep(10);
}
else
{
@ -139,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,7 +2,7 @@
using System.IO.Ports;
using System.Windows;
namespace WpfMaiTouchEmulator;
namespace WpfMaiTouchEmulator.Managers;
internal class VirtualComPortManager
{
@ -21,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)

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())));
@ -94,5 +94,65 @@ namespace WpfMaiTouchEmulator.Properties {
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

@ -20,5 +20,20 @@
<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>

View File

@ -7,11 +7,11 @@
# デモ
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

View File

@ -13,11 +13,11 @@ The app uses com0com to install a virtual COM port pairing of COM3 - COM23. The
# 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

View File

@ -7,11 +7,11 @@
# 演示
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

Binary file not shown.

Binary file not shown.

View File

@ -60,6 +60,15 @@ namespace WpfMaiTouchEmulator.Resources {
}
}
/// <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>
@ -96,6 +105,42 @@ namespace WpfMaiTouchEmulator.Resources {
}
}
/// <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>
@ -105,6 +150,15 @@ namespace WpfMaiTouchEmulator.Resources {
}
}
/// <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>
@ -141,6 +195,33 @@ namespace WpfMaiTouchEmulator.Resources {
}
}
/// <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>
@ -177,6 +258,24 @@ namespace WpfMaiTouchEmulator.Resources {
}
}
/// <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>
@ -205,7 +304,16 @@ namespace WpfMaiTouchEmulator.Resources {
}
/// <summary>
/// Looks up a localized string similar to Recieved.
/// 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 {
@ -222,6 +330,15 @@ namespace WpfMaiTouchEmulator.Resources {
}
}
/// <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>

View File

@ -117,6 +117,9 @@
<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>
@ -129,9 +132,24 @@
<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>
@ -144,6 +162,15 @@
<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>
@ -156,6 +183,12 @@
<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>
@ -165,12 +198,18 @@
<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>

View File

@ -117,6 +117,9 @@
<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>
@ -129,9 +132,24 @@
<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>
@ -144,6 +162,15 @@
<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>
@ -156,6 +183,12 @@
<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>
@ -165,12 +198,18 @@
<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>Recieved</value>
<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>

View File

@ -117,6 +117,9 @@
<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>
@ -129,9 +132,24 @@
<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>
@ -144,6 +162,15 @@
<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>
@ -156,6 +183,12 @@
<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>
@ -165,12 +198,18 @@
<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>

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="{Binding LbTouchPanelDrag}" d: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="{Binding LbTouchPanelResize}" d: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,15 +2,32 @@
<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>
<PackageReference Include="System.Drawing.Common" Version="8.0.1" />
<PackageReference Include="System.IO.Ports" Version="8.0.0" />
<None Remove="Assets\conicalGradient.png" />
</ItemGroup>
<ItemGroup>
<Content Include="Assets\icon.ico" />
</ItemGroup>
<ItemGroup>
<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>
<ItemGroup>