Add ability for emulator to emulate keyboard events for ring button emulation

pull/7/head
LeapwardKoex 2024-06-12 22:37:09 +12:00
parent b73e88f1da
commit 48cdfd0e72
13 changed files with 165 additions and 4 deletions

View File

@ -25,6 +25,9 @@
<setting name="UserLanguage" serializeAs="String">
<value />
</setting>
<setting name="IsRingButtonEmulationEnabled" serializeAs="String">
<value>False</value>
</setting>
</WpfMaiTouchEmulator.Properties.Settings>
</userSettings>
</configuration>

View File

@ -3,7 +3,7 @@ using System.Windows.Controls;
namespace WpfMaiTouchEmulator;
enum TouchValue: long
public enum TouchValue: long
{
A1 = 1 << 0, // 2^0
A2 = 1 << 1, // 2^1

View File

@ -5,7 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Closing="MainWindow_Closing"
Title="MainWindow" Height="360" Width="500" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
Title="MainWindow" Height="384" Width="500" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
<StackPanel VerticalAlignment="Top">
<Menu Width="Auto" Height="20">
<MenuItem Header="{Binding LbMenuCategoryHelp}" d:Header="_Help">
@ -38,6 +38,7 @@
<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}"/>
<CheckBox x:Name="emulateRingButtons" Content="{Binding LbEmulateRingButtons}" d:Content="Emulate ring buttons" HorizontalAlignment="Left" Margin="10,299,0,0" VerticalAlignment="Top" Height="17" Width="224" IsChecked="{Binding IsRingButtonEmulationEnabled}" ToolTip="{Binding LbEmulateRingButtonsTT}" Click="emulateRingButtons_Click"/>
<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"/>

View File

@ -19,6 +19,7 @@ public partial class MainWindow : Window
IsAutomaticPortConnectingEnabled = Properties.Settings.Default.IsAutomaticPortConnectingEnabled,
IsAutomaticPositioningEnabled = Properties.Settings.Default.IsAutomaticPositioningEnabled,
IsExitWithSinmaiEnabled = Properties.Settings.Default.IsExitWithSinmaiEnabled,
IsRingButtonEmulationEnabled = Properties.Settings.Default.IsRingButtonEmulationEnabled
};
Title = "Mai Touch Emulator";
@ -241,4 +242,14 @@ 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);
}
}

View File

@ -40,6 +40,10 @@ public class MainWindowViewModel : INotifyPropertyChanged
{
get; set;
}
public string LbEmulateRingButtons
{
get; set;
}
public string LbInstallComPort
{
get; set;
@ -174,12 +178,18 @@ public class MainWindowViewModel : INotifyPropertyChanged
get;
private set;
}
public string? LbEmulateRingButtonsTT
{
get;
private set;
}
private bool _isAutomaticPortConnectingEnabled;
private bool _isDebugEnabled;
private bool _isAutomaticPositioningEnabled;
private bool _isExitWithSinmaiEnabled;
private CultureInfo _selectedLanguage;
private bool _isRingButtonEmulationEnabled;
private readonly ResourceManager resourceManager;
private readonly CultureInfo cultureInfo;
@ -248,6 +258,17 @@ public class MainWindowViewModel : INotifyPropertyChanged
}
}
public bool IsRingButtonEmulationEnabled
{
get => _isRingButtonEmulationEnabled;
set
{
_isRingButtonEmulationEnabled = value;
OnPropertyChanged();
}
}
public CultureInfo SelectedLanguage
{
get => _selectedLanguage;
@ -282,6 +303,7 @@ public class MainWindowViewModel : INotifyPropertyChanged
LbConnectToPort = resourceManager.GetString("lbConnectToPort");
LbDebugMode = resourceManager.GetString("lbDebugMode");
LbExitWithSinmai = resourceManager.GetString("lbExitWithSinmai");
LbEmulateRingButtons = resourceManager.GetString("lbEmulateRingButtons");
LbInstallComPort = resourceManager.GetString("lbInstallComPort");
LbLanguageDropdown = resourceManager.GetString("lbLanguageDropdown");
LbListComPorts = resourceManager.GetString("lbListComPorts");
@ -294,6 +316,7 @@ public class MainWindowViewModel : INotifyPropertyChanged
LbAutoPortConnectingTT = resourceManager.GetString("lbAutoPortConnectingTT");
LbAutoSensorPositioningTT = resourceManager.GetString("lbAutoSensorPositioningTT");
LbExitWithSinmaiTT = resourceManager.GetString("lbExitWithSinmaiTT");
LbEmulateRingButtonsTT = resourceManager.GetString("lbEmulateRingButtonsTT");
LbMenuCategoryHelp = resourceManager.GetString("lbMenuCategoryHelp");
LbMenuItemSetup = resourceManager.GetString("lbMenuItemSetup");

View File

@ -94,5 +94,17 @@ namespace WpfMaiTouchEmulator.Properties {
this["UserLanguage"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool IsRingButtonEmulationEnabled {
get {
return ((bool)(this["IsRingButtonEmulationEnabled"]));
}
set {
this["IsRingButtonEmulationEnabled"] = value;
}
}
}
}

View File

@ -20,5 +20,8 @@
<Setting Name="UserLanguage" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="IsRingButtonEmulationEnabled" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
</Settings>
</SettingsFile>

View File

@ -141,6 +141,24 @@ namespace WpfMaiTouchEmulator.Resources {
}
}
/// <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>

View File

@ -144,6 +144,12 @@
<data name="lbDebugMode" 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>

View File

@ -144,6 +144,12 @@
<data name="lbDebugMode" xml:space="preserve">
<value>Debug mode</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>

View File

@ -144,6 +144,12 @@
<data name="lbDebugMode" 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>

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;
public partial class RingButtonEmulator
{
[DllImport("user32.dll")]
static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr 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, UIntPtr.Zero);
}
}
public static void ReleaseButton(TouchValue touchValue)
{
if (touchRingMapping.TryGetValue(touchValue, out var vk))
{
keybd_event(vk, 0, KEYEVENTF_KEYUP, UIntPtr.Zero);
}
}
public static void ReleaseAllButtons()
{
foreach (var vk in touchRingMapping.Values)
{
keybd_event(vk, 0, KEYEVENTF_KEYUP, UIntPtr.Zero);
}
}
}

View File

@ -17,6 +17,7 @@ public partial class TouchPanel : Window
private readonly TouchPanelPositionManager _positionManager;
private List<Polygon> buttons = [];
private bool isDebugEnabled = Properties.Settings.Default.IsDebugEnabled;
private bool isRingButtonEmulationEnabled = Properties.Settings.Default.IsRingButtonEmulationEnabled;
private enum ResizeDirection
{
@ -106,7 +107,15 @@ public partial class TouchPanel : Window
{
// Highlight the element and add it to the active touches tracking.
HighlightElement(element, true);
var touchValue = (TouchValue)element.Tag;
if (isRingButtonEmulationEnabled && RingButtonEmulator.HasRingButtonMapping((TouchValue)element.Tag))
{
RingButtonEmulator.PressButton((TouchValue)element.Tag);
}
else
{
onTouch?.Invoke((TouchValue)element.Tag);
}
activeTouches[e.TouchDevice.Id] = element;
}
e.Handled = true;
@ -149,6 +158,7 @@ public partial class TouchPanel : Window
if (activeTouches.TryGetValue(e.TouchDevice.Id, out var element))
{
HighlightElement(element, false);
RingButtonEmulator.ReleaseButton((TouchValue)element.Tag);
onRelease?.Invoke((TouchValue)element.Tag);
activeTouches.Remove(e.TouchDevice.Id);
}
@ -165,6 +175,7 @@ public partial class TouchPanel : Window
onRelease?.Invoke((TouchValue)element.Tag);
}
activeTouches.Clear();
RingButtonEmulator.ReleaseAllButtons();
}
public void SetDebugMode(bool enabled)
@ -176,6 +187,11 @@ public partial class TouchPanel : Window
});
}
public void SetEmulateRingButton(bool enabled)
{
isRingButtonEmulationEnabled = enabled;
}
private void HighlightElement(Polygon element, bool highlight)
{
if (isDebugEnabled)