Added Macro functionality, udpated readme

This commit is contained in:
2026-03-29 22:18:49 +02:00
parent 747bec985d
commit 2eb43826ce
8 changed files with 975 additions and 174 deletions
+421 -121
View File
@@ -7,7 +7,7 @@
// Host Command → Zahlen-Eingabe (Command-ID)
// Keine Aktion → nichts
//
// Optional (nur MX-Buttons): LED-Basisfarbe
// Optional (nur MX-Buttons): LED-Basisfarbe + LED-Animation + Geschwindigkeit
namespace VersaGUI;
@@ -16,6 +16,8 @@ public class ActionDialog : Form
// ── Ergebnis ──────────────────────────────────────────────────────────────
public DeviceAction ResultAction { get; private set; }
public Color ResultColor { get; private set; }
public LedAnimType ResultAnim { get; private set; }
public ushort ResultPeriod { get; private set; }
// ── Controls ──────────────────────────────────────────────────────────────
private readonly ComboBox _typeCombo;
@@ -38,86 +40,132 @@ public class ActionDialog : Form
private readonly Button _colorBtn;
private Color _color;
// LED-Animation
private readonly Panel _animPanel;
private readonly ComboBox _animCombo;
private readonly ComboBox _periodCombo;
// Makro
private readonly Panel _macroPanel;
private readonly MacroTable _macros;
private readonly int _macroSlot;
// Je Step: [CaptureBtn, CheckCtrl, CheckShift, CheckAlt]
private readonly Button[] _stepBtns = new Button[4];
private readonly CheckBox[] _stepCtrl = new CheckBox[4];
private readonly CheckBox[] _stepShift = new CheckBox[4];
private readonly CheckBox[] _stepAlt = new CheckBox[4];
private readonly byte[] _stepKeycodes = new byte[4];
private int _captureStep = -1; // -1 = nicht im Capture-Modus
// Capture-Zustand
private bool _capturing;
private byte _hidKeycode; // 0 = nicht belegt
// Layout
private readonly bool _showColor;
private Button _okBtn = null!;
private Button _cancelBtn = null!;
// ── Lookup-Tabellen ───────────────────────────────────────────────────────
// Windows VK → HID Keyboard Usage (USB HID Usage Table 0x07)
private static readonly Dictionary<Keys, byte> s_vkToHid = new()
// P/Invoke: Windows-API für layout-unabhängige Scan-Code-Konvertierung
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern uint MapVirtualKey(uint uCode, uint uMapType);
[System.Runtime.InteropServices.DllImport("user32.dll",
CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
private static extern int GetKeyNameText(int lParam,
System.Text.StringBuilder lpString, int nSize);
// Scan-Code (PS/2 Set 1) → HID Usage (physische Tastenposition, Layout-unabhängig).
// Korrekt für QWERTZ, AZERTY und andere Layouts: Ö = Scan 0x27 → HID 0x33 (;-Position)
private static readonly Dictionary<byte, byte> s_scanToHid = new()
{
// Buchstaben
[Keys.A]=0x04,[Keys.B]=0x05,[Keys.C]=0x06,[Keys.D]=0x07,
[Keys.E]=0x08,[Keys.F]=0x09,[Keys.G]=0x0A,[Keys.H]=0x0B,
[Keys.I]=0x0C,[Keys.J]=0x0D,[Keys.K]=0x0E,[Keys.L]=0x0F,
[Keys.M]=0x10,[Keys.N]=0x11,[Keys.O]=0x12,[Keys.P]=0x13,
[Keys.Q]=0x14,[Keys.R]=0x15,[Keys.S]=0x16,[Keys.T]=0x17,
[Keys.U]=0x18,[Keys.V]=0x19,[Keys.W]=0x1A,[Keys.X]=0x1B,
[Keys.Y]=0x1C,[Keys.Z]=0x1D,
// Ziffernreihe
[Keys.D1]=0x1E,[Keys.D2]=0x1F,[Keys.D3]=0x20,[Keys.D4]=0x21,
[Keys.D5]=0x22,[Keys.D6]=0x23,[Keys.D7]=0x24,[Keys.D8]=0x25,
[Keys.D9]=0x26,[Keys.D0]=0x27,
// Steuerung
[Keys.Return]=0x28,[Keys.Escape]=0x29,[Keys.Back]=0x2A,
[Keys.Tab]=0x2B, [Keys.Space]=0x2C, [Keys.Delete]=0x4C,
[Keys.Insert]=0x49,[Keys.Home]=0x4A, [Keys.PageUp]=0x4B,
[Keys.End]=0x4D, [Keys.PageDown]=0x4E,
[Keys.Right]=0x4F,[Keys.Left]=0x50, [Keys.Down]=0x51,[Keys.Up]=0x52,
[Keys.CapsLock]=0x39,
[Keys.PrintScreen]=0x46,[Keys.Scroll]=0x47,[Keys.Pause]=0x48,
[Keys.NumLock]=0x53,
// F-Tasten
[Keys.F1]=0x3A,[Keys.F2]=0x3B,[Keys.F3]=0x3C,[Keys.F4]=0x3D,
[Keys.F5]=0x3E,[Keys.F6]=0x3F,[Keys.F7]=0x40,[Keys.F8]=0x41,
[Keys.F9]=0x42,[Keys.F10]=0x43,[Keys.F11]=0x44,[Keys.F12]=0x45,
// Sonderzeichen (QWERTZ-kompatibel, basiert auf US-HID-Positionen)
[Keys.OemMinus]=0x2D, [Keys.Oemplus]=0x2E,
[Keys.OemOpenBrackets]=0x2F, [Keys.OemCloseBrackets]=0x30,
[Keys.OemPipe]=0x31, [Keys.OemSemicolon]=0x33,
[Keys.OemQuotes]=0x34, [Keys.Oemtilde]=0x35,
[Keys.Oemcomma]=0x36, [Keys.OemPeriod]=0x37,
[Keys.OemQuestion]=0x38,
// Numpad
[Keys.NumPad1]=0x59,[Keys.NumPad2]=0x5A,[Keys.NumPad3]=0x5B,
[Keys.NumPad4]=0x5C,[Keys.NumPad5]=0x5D,[Keys.NumPad6]=0x5E,
[Keys.NumPad7]=0x5F,[Keys.NumPad8]=0x60,[Keys.NumPad9]=0x61,
[Keys.NumPad0]=0x62,[Keys.Decimal]=0x63,
[Keys.Multiply]=0x55,[Keys.Add]=0x57,
[Keys.Subtract]=0x56,[Keys.Divide]=0x54,
[0x01]=0x29, // Escape
[0x02]=0x1E,[0x03]=0x1F,[0x04]=0x20,[0x05]=0x21,[0x06]=0x22,
[0x07]=0x23,[0x08]=0x24,[0x09]=0x25,[0x0A]=0x26,[0x0B]=0x27, // 10
[0x0C]=0x2D,[0x0D]=0x2E, // -/ß =/´
[0x0E]=0x2A, // Backspace
[0x0F]=0x2B, // Tab
[0x10]=0x14,[0x11]=0x1A,[0x12]=0x08,[0x13]=0x15,[0x14]=0x17, // Q W E R T
[0x15]=0x1C,[0x16]=0x18,[0x17]=0x0C,[0x18]=0x12,[0x19]=0x13, // Z/Y U I O P
[0x1A]=0x2F,[0x1B]=0x30, // Ü +
[0x1C]=0x28, // Enter
[0x1E]=0x04,[0x1F]=0x16,[0x20]=0x07,[0x21]=0x09,[0x22]=0x0A, // A S D F G
[0x23]=0x0B,[0x24]=0x0D,[0x25]=0x0E,[0x26]=0x0F, // H J K L
[0x27]=0x33,[0x28]=0x34,[0x29]=0x35, // Ö Ä ^
[0x2B]=0x31, // #
[0x2C]=0x1D,[0x2D]=0x1B,[0x2E]=0x06,[0x2F]=0x19,[0x30]=0x05, // Y/Z X C V B
[0x31]=0x11,[0x32]=0x10,[0x33]=0x36,[0x34]=0x37,[0x35]=0x38, // N M , . -
[0x37]=0x55, // Numpad *
[0x39]=0x2C, // Space
[0x3A]=0x39, // CapsLock
[0x3B]=0x3A,[0x3C]=0x3B,[0x3D]=0x3C,[0x3E]=0x3D,[0x3F]=0x3E, // F1F5
[0x40]=0x3F,[0x41]=0x40,[0x42]=0x41,[0x43]=0x42,[0x44]=0x43, // F6F10
[0x45]=0x53,[0x46]=0x47, // NumLock ScrollLock
[0x47]=0x5F,[0x48]=0x60,[0x49]=0x61,[0x4A]=0x56, // Num7 8 9 -
[0x4B]=0x5C,[0x4C]=0x5D,[0x4D]=0x5E,[0x4E]=0x57, // Num4 5 6 +
[0x4F]=0x59,[0x50]=0x5A,[0x51]=0x5B,[0x52]=0x62,[0x53]=0x63, // Num1 2 3 0 .
[0x56]=0x64, // Non-US \ (< > auf QWERTZ)
[0x57]=0x44,[0x58]=0x45, // F11 F12
};
// HID-Code → lesbarer Name (für Capture-Button-Beschriftung)
private static readonly Dictionary<byte, string> s_hidNames = new()
// HID → Scan-Code (umgekehrte Tabelle, für Anzeigenamen)
private static readonly Dictionary<byte, byte> s_hidToScan =
s_scanToHid.ToDictionary(kv => kv.Value, kv => kv.Key);
// Direkte VK→HID-Zuordnung nur für erweiterte Navigationstasten.
// MapVirtualKey gibt für diese den Numpad-Scan-Code zurück (falsch).
private static readonly Dictionary<Keys, byte> s_extVkToHid = new()
{
[0x04]="A",[0x05]="B",[0x06]="C",[0x07]="D",[0x08]="E",[0x09]="F",
[0x0A]="G",[0x0B]="H",[0x0C]="I",[0x0D]="J",[0x0E]="K",[0x0F]="L",
[0x10]="M",[0x11]="N",[0x12]="O",[0x13]="P",[0x14]="Q",[0x15]="R",
[0x16]="S",[0x17]="T",[0x18]="U",[0x19]="V",[0x1A]="W",[0x1B]="X",
[0x1C]="Y",[0x1D]="Z",
[0x1E]="1",[0x1F]="2",[0x20]="3",[0x21]="4",[0x22]="5",
[0x23]="6",[0x24]="7",[0x25]="8",[0x26]="9",[0x27]="0",
[0x28]="Enter", [0x29]="Escape", [0x2A]="Backspace",
[0x2B]="Tab", [0x2C]="Leertaste", [0x2D]="-",
[0x2E]="=", [0x2F]="[", [0x30]="]",
[0x31]="\\", [0x33]=";", [0x34]="'",
[0x35]="`", [0x36]=",", [0x37]=".",
[0x38]="/", [0x39]="CapsLock",
[0x3A]="F1", [0x3B]="F2", [0x3C]="F3", [0x3D]="F4",
[0x3E]="F5", [0x3F]="F6", [0x40]="F7", [0x41]="F8",
[0x42]="F9", [0x43]="F10",[0x44]="F11",[0x45]="F12",
[0x46]="Druck", [0x47]="Rollen", [0x48]="Pause",
[0x49]="Einfg", [0x4A]="Pos1", [0x4B]="Bild Auf",
[0x4C]="Entf", [0x4D]="Ende", [0x4E]="Bild Ab",
[0x4F]="Rechts", [0x50]="Links", [0x51]="Runter", [0x52]="Hoch",
[0x53]="NumLock",[0x54]="Num/", [0x55]="Num*",
[0x56]="Num-", [0x57]="Num+",
[0x59]="Num1", [0x5A]="Num2", [0x5B]="Num3", [0x5C]="Num4",
[0x5D]="Num5", [0x5E]="Num6", [0x5F]="Num7", [0x60]="Num8",
[0x61]="Num9", [0x62]="Num0", [0x63]="Num.",
[Keys.Up]=0x52, [Keys.Down]=0x51, [Keys.Left]=0x50,
[Keys.Right]=0x4F, [Keys.Home]=0x4A, [Keys.End]=0x4D,
[Keys.PageUp]=0x4B, [Keys.PageDown]=0x4E, [Keys.Insert]=0x49,
[Keys.Delete]=0x4C, [Keys.Pause]=0x48, [Keys.Scroll]=0x47,
[Keys.PrintScreen]=0x46,
};
// Windows VK → HID via Scan-Code (layout-unabhängig).
// Ö auf QWERTZ → Scan 0x27 → HID 0x33 (;-Position auf US) → Board sendet HID 0x33
// → Zielrechner mit QWERTZ-Layout erzeugt korrekt Ö.
private static byte VkToHid(Keys vk)
{
if (s_extVkToHid.TryGetValue(vk, out byte hExt)) return hExt;
byte sc = (byte)MapVirtualKey((uint)(vk & Keys.KeyCode), 0u /* VK→SC */);
return s_scanToHid.TryGetValue(sc, out byte hid) ? hid : (byte)0;
}
// HID-Code → lesbarer Tastenname laut aktivem Windows-Tastaturlayout.
// Gibt auf QWERTZ "Ö" für HID 0x33, auf US-Layout ";" zurück.
private static string HidKeyName(byte hid)
{
// Sondertasten ohne Scan-Code-Eintrag
var special = (Dictionary<byte, string>)new Dictionary<byte, string>
{
[0x28]="Enter", [0x29]="Escape", [0x2A]="Backspace", [0x2B]="Tab",
[0x2C]="Leer", [0x39]="Caps", [0x46]="Druck", [0x47]="Rollen",
[0x48]="Pause", [0x49]="Einfg", [0x4A]="Pos1", [0x4B]="Bild↑",
[0x4C]="Entf", [0x4D]="Ende", [0x4E]="Bild↓",
[0x4F]="→", [0x50]="←", [0x51]="↓", [0x52]="↑",
[0x53]="NumLock",[0x54]="Num/", [0x55]="Num*", [0x56]="Num-",
[0x57]="Num+", [0x58]="NumEnter",
[0x59]="Num1", [0x5A]="Num2", [0x5B]="Num3", [0x5C]="Num4",
[0x5D]="Num5", [0x5E]="Num6", [0x5F]="Num7", [0x60]="Num8",
[0x61]="Num9", [0x62]="Num0", [0x63]="Num.",
[0x3A]="F1",[0x3B]="F2",[0x3C]="F3",[0x3D]="F4",[0x3E]="F5",
[0x3F]="F6",[0x40]="F7",[0x41]="F8",[0x42]="F9",[0x43]="F10",
[0x44]="F11",[0x45]="F12",
};
if (special.TryGetValue(hid, out string? name)) return name;
// Für Zeichen-Tasten: GetKeyNameText gibt den lokalisierten Namen zurück
if (!s_hidToScan.TryGetValue(hid, out byte sc)) return $"0x{hid:X2}";
int lParam = sc << 16;
var sb = new System.Text.StringBuilder(32);
GetKeyNameText(lParam, sb, sb.Capacity);
return sb.Length > 0 ? sb.ToString() : $"0x{hid:X2}";
}
// HID Consumer Usage IDs (Usage Page 0x0C)
private static readonly (ushort Usage, string Name)[] s_consumer =
{
@@ -137,11 +185,43 @@ public class ActionDialog : Form
// ── Konstruktor ───────────────────────────────────────────────────────────
public ActionDialog(DeviceAction action, Color ledColor, bool showColor = true)
// Animations-ComboBox-Einträge: (Anzeigename, LedAnimType, Farbe sinnvoll?)
private static readonly (string Name, LedAnimType Type, bool ShowColor)[] s_anims =
{
("Statisch", LedAnimType.Static, true),
("Blinken", LedAnimType.Blink, true),
("Pulsieren", LedAnimType.Pulse, true),
("Regenbogen", LedAnimType.ColorCycle, false),
};
// Geschwindigkeits-Presets: (Anzeigename, Millisekunden)
private static readonly (string Name, ushort Ms)[] s_periods =
{
("Schnell (0.5s)", 500),
("Mittel (1s)", 1000),
("Langsam (2s)", 2000),
("Sehr langsam (4s)", 4000),
};
public ActionDialog(DeviceAction action, Color ledColor,
LedAnimType ledAnim = LedAnimType.ColorCycle,
ushort ledPeriod = 4000,
MacroTable? macros = null,
int macroSlot = 0,
bool showColor = true)
{
ResultAction = action.Clone();
ResultColor = ledColor;
ResultAnim = ledAnim;
ResultPeriod = ledPeriod;
_color = ledColor;
_macros = macros ?? new MacroTable();
_macroSlot = macroSlot;
_showColor = showColor;
// Bestehende Makro-Steps aus der Tabelle laden
for (int i = 0; i < 4; i++)
_stepKeycodes[i] = _macros.Steps[_macroSlot, i].Keycode;
// Formular-Grundeinstellungen
Text = "Aktion bearbeiten";
@@ -156,20 +236,20 @@ public class ActionDialog : Form
_typeCombo = new ComboBox
{
Location = new Point(80, 12),
Width = 208,
Width = 316,
DropDownStyle = ComboBoxStyle.DropDownList,
};
_typeCombo.Items.AddRange(new object[]
{ "Keine Aktion", "HID Tastatur", "HID Consumer", "Host Command" });
{ "Keine Aktion", "HID Tastatur", "HID Consumer", "Host Command", "Makro" });
_typeCombo.SelectedIndex = (int)action.Type;
_typeCombo.SelectedIndexChanged += OnTypeChanged;
// ── HID-Tastatur-Panel ────────────────────────────────────────────────
_hidKeyPanel = new Panel { Location = new Point(12, 48), Size = new Size(280, 110) };
_hidKeyPanel = new Panel { Location = new Point(12, 48), Size = new Size(396, 110) };
_captureBtn = new Button
{
Size = new Size(280, 48),
Size = new Size(396, 48),
Location = new Point(0, 0),
FlatStyle = FlatStyle.Flat,
Font = new Font(Font.FontFamily, 12, FontStyle.Regular),
@@ -213,11 +293,11 @@ public class ActionDialog : Form
RefreshCaptureBtn(); // erst nach Checkbox-Initialisierung aufrufen
// ── Consumer-Panel ────────────────────────────────────────────────────
_consumerPanel = new Panel { Location = new Point(12, 48), Size = new Size(280, 30) };
_consumerPanel = new Panel { Location = new Point(12, 48), Size = new Size(396, 30) };
_consumerCombo = new ComboBox
{
Location = new Point(0, 0),
Width = 280,
Width = 396,
DropDownStyle = ComboBoxStyle.DropDownList,
};
foreach (var (_, name) in s_consumer)
@@ -230,12 +310,12 @@ public class ActionDialog : Form
_consumerPanel.Controls.Add(_consumerCombo);
// ── Host-Command-Panel ────────────────────────────────────────────────
_cmdPanel = new Panel { Location = new Point(12, 48), Size = new Size(280, 30) };
_cmdPanel = new Panel { Location = new Point(12, 48), Size = new Size(396, 30) };
var cmdLabel = new Label { Text = "Command-ID:", Location = new Point(0, 8), AutoSize = true };
_cmdBox = new TextBox
{
Location = new Point(90, 4),
Width = 190,
Width = 306,
Text = action.Type == ActionType.HostCommand ? $"{action.Data}" : "0",
};
_cmdPanel.Controls.AddRange(new Control[] { cmdLabel, _cmdBox });
@@ -244,14 +324,14 @@ public class ActionDialog : Form
_colorPanel = new Panel
{
Location = new Point(12, 168),
Size = new Size(280, 30),
Size = new Size(396, 30),
Visible = showColor,
};
var colorLabel = new Label { Text = "LED-Farbe:", Location = new Point(0, 8), AutoSize = true };
_colorBtn = new Button
{
Location = new Point(80, 2),
Size = new Size(200, 26),
Size = new Size(316, 26),
BackColor = ledColor,
FlatStyle = FlatStyle.Flat,
Text = string.Empty,
@@ -260,37 +340,126 @@ public class ActionDialog : Form
_colorBtn.Click += OnColorClick;
_colorPanel.Controls.AddRange(new Control[] { colorLabel, _colorBtn });
// ── Buttons Fußzeile ──────────────────────────────────────────────────
int footerY = showColor ? 208 : 168;
var okBtn = new Button
// ── Animation-Panel ───────────────────────────────────────────────────
_animPanel = new Panel
{
Location = new Point(12, 208),
Size = new Size(396, 58),
Visible = showColor,
};
var animLabel = new Label { Text = "Animation:", Location = new Point(0, 8), AutoSize = true };
_animCombo = new ComboBox
{
Location = new Point(80, 4),
Width = 316,
DropDownStyle = ComboBoxStyle.DropDownList,
};
foreach (var (name, _, _) in s_anims)
_animCombo.Items.Add(name);
int animIdx = Array.FindIndex(s_anims, a => a.Type == ledAnim);
_animCombo.SelectedIndex = animIdx >= 0 ? animIdx : 0;
_animCombo.SelectedIndexChanged += OnAnimChanged;
var periodLabel = new Label { Text = "Tempo:", Location = new Point(0, 36), AutoSize = true };
_periodCombo = new ComboBox
{
Location = new Point(80, 32),
Width = 316,
DropDownStyle = ComboBoxStyle.DropDownList,
};
foreach (var (name, _) in s_periods)
_periodCombo.Items.Add(name);
int periodIdx = Array.FindIndex(s_periods, p => p.Ms == ledPeriod);
_periodCombo.SelectedIndex = periodIdx >= 0 ? periodIdx : 3; // Default: sehr langsam
_animPanel.Controls.AddRange(new Control[]
{ animLabel, _animCombo, periodLabel, _periodCombo });
// ── Makro-Panel (4 Steps) ─────────────────────────────────────────────
_macroPanel = new Panel { Location = new Point(12, 48), Size = new Size(396, 4 * 30 + 22) };
var macroHint = new Label
{
Text = "Klicke einen Step, dann Taste drücken. Leere Steps werden übersprungen.",
Location = new Point(0, 0),
Size = new Size(396, 28),
ForeColor = SystemColors.GrayText,
Font = new Font(Font.FontFamily, 8),
};
_macroPanel.Controls.Add(macroHint);
for (int step = 0; step < 4; step++)
{
int s = step;
int y2 = 30 + step * 30;
var stepLabel = new Label
{
Text = $"Step {step + 1}:",
Location = new Point(0, y2 + 6),
AutoSize = true,
};
_stepBtns[step] = new Button
{
Location = new Point(50, y2),
Size = new Size(160, 24),
FlatStyle = FlatStyle.Flat,
Cursor = Cursors.Hand,
};
_stepBtns[step].FlatAppearance.BorderColor = Color.DarkGray;
_stepBtns[step].Click += (_, _) => StartMacroCapture(s);
_stepCtrl[step] = new CheckBox { Text = "Strg", Location = new Point(218, y2 + 4), AutoSize = true };
_stepShift[step] = new CheckBox { Text = "Shift", Location = new Point(268, y2 + 4), AutoSize = true };
_stepAlt[step] = new CheckBox { Text = "Alt", Location = new Point(326, y2 + 4), AutoSize = true };
// Bestehende Werte aus Makro-Tabelle laden
_stepCtrl[step].Checked = (_macros.Steps[_macroSlot, step].Modifier & 0x01) != 0;
_stepShift[step].Checked = (_macros.Steps[_macroSlot, step].Modifier & 0x02) != 0;
_stepAlt[step].Checked = (_macros.Steps[_macroSlot, step].Modifier & 0x04) != 0;
_stepCtrl[step].CheckedChanged += (_, _) => RefreshStepBtn(s);
_stepShift[step].CheckedChanged += (_, _) => RefreshStepBtn(s);
_stepAlt[step].CheckedChanged += (_, _) => RefreshStepBtn(s);
_macroPanel.Controls.AddRange(new Control[]
{ stepLabel, _stepBtns[step], _stepCtrl[step], _stepShift[step], _stepAlt[step] });
RefreshStepBtn(step);
}
// ── Buttons Fußzeile ─────────────────────────────────────────────────
_okBtn = new Button
{
Text = "OK",
DialogResult = DialogResult.OK,
Location = new Point(112, footerY),
Width = 80,
};
okBtn.Click += OnOk;
_okBtn.Click += OnOk;
var cancelBtn = new Button
_cancelBtn = new Button
{
Text = "Abbrechen",
DialogResult = DialogResult.Cancel,
Location = new Point(200, footerY),
Width = 92,
};
AcceptButton = okBtn;
CancelButton = cancelBtn;
ClientSize = new Size(304, footerY + 44);
AcceptButton = _okBtn;
CancelButton = _cancelBtn;
Controls.AddRange(new Control[]
{
typeLabel, _typeCombo,
_hidKeyPanel, _consumerPanel, _cmdPanel, _colorPanel,
okBtn, cancelBtn,
_hidKeyPanel, _consumerPanel, _cmdPanel, _macroPanel,
_colorPanel, _animPanel,
_okBtn, _cancelBtn,
});
UpdatePanelVisibility();
UpdatePanelVisibility(); // setzt Visibility + ruft UpdateLayout auf
}
// ── Panel-Sichtbarkeit ────────────────────────────────────────────────────
@@ -301,12 +470,62 @@ public class ActionDialog : Form
UpdatePanelVisibility();
}
private void OnAnimChanged(object? sender, EventArgs e)
{
UpdateAnimVisibility();
}
private void UpdatePanelVisibility()
{
var t = (ActionType)_typeCombo.SelectedIndex;
_hidKeyPanel.Visible = t == ActionType.HidKey;
_consumerPanel.Visible = t == ActionType.HidConsumer;
_cmdPanel.Visible = t == ActionType.HostCommand;
_macroPanel.Visible = t == ActionType.Macro;
_colorPanel.Visible = _showColor;
_animPanel.Visible = _showColor;
UpdateAnimVisibility();
UpdateLayout();
}
// Positioniert LED-Panels dynamisch unterhalb des aktiven Typ-Panels
// und passt die Formulargröße an.
private void UpdateLayout()
{
var t = (ActionType)_typeCombo.SelectedIndex;
int contentH = t switch
{
ActionType.HidKey => 110,
ActionType.HidConsumer => 30,
ActionType.HostCommand => 30,
ActionType.Macro => 4 * 30 + 22,
_ => 0,
};
int ledY = 48 + contentH + 10;
int footerY = ledY;
if (_showColor)
{
_colorPanel.Location = new Point(12, ledY);
_animPanel.Location = new Point(12, ledY + 38);
footerY = ledY + 38 + 62;
}
_okBtn.Location = new Point(224, footerY);
_cancelBtn.Location = new Point(312, footerY);
ClientSize = new Size(420, footerY + 44);
}
private void UpdateAnimVisibility()
{
if (_animPanel.Visible && _animCombo.SelectedIndex >= 0)
{
var (_, animType, showColorFlag) = s_anims[_animCombo.SelectedIndex];
_colorBtn.Enabled = showColorFlag;
_periodCombo.Enabled = animType != LedAnimType.Static;
}
}
// ── Tasten-Erfassung ──────────────────────────────────────────────────────
@@ -317,6 +536,40 @@ public class ActionDialog : Form
else StartCapture();
}
// ── Makro-Step-Capture ────────────────────────────────────────────────────
private void StartMacroCapture(int step)
{
_captureStep = step;
_stepBtns[step].Text = "Taste drücken...";
_stepBtns[step].BackColor = Color.FromArgb(255, 220, 80);
_stepBtns[step].ForeColor = Color.Black;
}
private void RefreshStepBtn(int step)
{
if (_captureStep == step) return; // gerade im Capture-Modus
byte kc = _stepKeycodes[step];
if (kc == 0)
{
_stepBtns[step].Text = "— (leer)";
_stepBtns[step].BackColor = SystemColors.Control;
_stepBtns[step].ForeColor = SystemColors.GrayText;
}
else
{
string name = HidKeyName(kc);
var parts = new List<string>();
if (_stepCtrl[step].Checked) parts.Add("Strg");
if (_stepShift[step].Checked) parts.Add("Shift");
if (_stepAlt[step].Checked) parts.Add("Alt");
parts.Add(name);
_stepBtns[step].Text = string.Join("+", parts);
_stepBtns[step].BackColor = SystemColors.Control;
_stepBtns[step].ForeColor = SystemColors.ControlText;
}
}
private void StartCapture()
{
_capturing = true;
@@ -334,14 +587,14 @@ public class ActionDialog : Form
private void RefreshCaptureBtn()
{
if (_hidKeycode != 0 && s_hidNames.TryGetValue(_hidKeycode, out string? kn))
if (_hidKeycode != 0)
{
var parts = new List<string>();
if (_chkCtrl.Checked) parts.Add("Strg");
if (_chkShift.Checked) parts.Add("Shift");
if (_chkAlt.Checked) parts.Add("Alt");
if (_chkWin.Checked) parts.Add("Win");
parts.Add(kn);
parts.Add(HidKeyName(_hidKeycode));
_captureBtn.Text = string.Join(" + ", parts);
}
else
@@ -353,41 +606,65 @@ public class ActionDialog : Form
_captureBtn.FlatAppearance.BorderColor = Color.DarkGray;
}
// Tasten werden form-weit abgefangen (KeyPreview = true)
protected override void OnKeyDown(KeyEventArgs e)
// ProcessCmdKey wird vor jeder Dialog-Verarbeitung aufgerufen (Enter, Pfeiltasten,
// Tab usw. werden normalerweise als "Dialog-Tasten" verschluckt, bevor OnKeyDown
// feuert). Daher hier die Capture-Logik so kommen auch ↑↓←→ und Enter an.
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (!_capturing) { base.OnKeyDown(e); return; }
const int WM_KEYDOWN = 0x0100;
const int WM_SYSKEYDOWN = 0x0104;
if (msg.Msg != WM_KEYDOWN && msg.Msg != WM_SYSKEYDOWN)
return base.ProcessCmdKey(ref msg, keyData);
if (!_capturing && _captureStep < 0)
return base.ProcessCmdKey(ref msg, keyData);
Keys vk = keyData & Keys.KeyCode;
bool ctrl = (keyData & Keys.Control) != 0;
bool shift = (keyData & Keys.Shift) != 0;
bool alt = (keyData & Keys.Alt) != 0;
// Reine Modifier-Tasten ignorieren auf echte Taste warten
if (e.KeyCode is Keys.ControlKey or Keys.LControlKey or Keys.RControlKey
or Keys.ShiftKey or Keys.LShiftKey or Keys.RShiftKey
or Keys.Menu or Keys.LMenu or Keys.RMenu
or Keys.LWin or Keys.RWin or Keys.Apps)
if (vk is Keys.ControlKey or Keys.LControlKey or Keys.RControlKey
or Keys.ShiftKey or Keys.LShiftKey or Keys.RShiftKey
or Keys.Menu or Keys.LMenu or Keys.RMenu
or Keys.LWin or Keys.RWin or Keys.Apps)
return true; // schlucken, aber Capture läuft weiter
// ── Makro-Step-Capture ────────────────────────────────────────────────
if (_captureStep >= 0)
{
e.Handled = true;
return;
int s = _captureStep;
_captureStep = -1;
if (vk == Keys.Escape)
{
_stepKeycodes[s] = 0;
}
else
{
_stepKeycodes[s] = VkToHid(vk);
_stepCtrl[s].Checked = ctrl;
_stepShift[s].Checked = shift;
_stepAlt[s].Checked = alt;
}
RefreshStepBtn(s);
return true;
}
// Escape bricht Erfassung ab (ohne Wert zu ändern)
if (e.KeyCode == Keys.Escape)
// ── HID-Tastatur-Capture ──────────────────────────────────────────────
if (vk == Keys.Escape)
{
StopCapture();
e.Handled = true;
e.SuppressKeyPress = true;
return;
}
// Modifikatoren übernehmen
_chkCtrl.Checked = e.Control;
_chkShift.Checked = e.Shift;
_chkAlt.Checked = e.Alt;
// VK → HID-Code
_hidKeycode = s_vkToHid.TryGetValue(e.KeyCode, out byte hid) ? hid : (byte)0;
StopCapture();
e.Handled = true;
e.SuppressKeyPress = true;
else
{
_chkCtrl.Checked = ctrl;
_chkShift.Checked = shift;
_chkAlt.Checked = alt;
_hidKeycode = VkToHid(vk);
StopCapture();
}
return true;
}
// ── Farbe ─────────────────────────────────────────────────────────────────
@@ -435,7 +712,30 @@ public class ActionDialog : Form
break;
}
if (type == ActionType.Macro)
{
// Steps in die Makro-Tabelle schreiben
for (int i = 0; i < 4; i++)
{
byte mod = 0;
if (_stepCtrl[i].Checked) mod |= 0x01;
if (_stepShift[i].Checked) mod |= 0x02;
if (_stepAlt[i].Checked) mod |= 0x04;
_macros.Steps[_macroSlot, i].Keycode = _stepKeycodes[i];
_macros.Steps[_macroSlot, i].Modifier = mod;
}
data = (ushort)_macroSlot;
}
ResultAction = new DeviceAction { Type = type, Data = data };
ResultColor = _color;
if (_animPanel.Visible && _animCombo.SelectedIndex >= 0)
{
ResultAnim = s_anims[_animCombo.SelectedIndex].Type;
ResultPeriod = _periodCombo.SelectedIndex >= 0
? s_periods[_periodCombo.SelectedIndex].Ms
: (ushort)4000;
}
}
}