diff --git a/ui/localization/messages/cy.json b/ui/localization/messages/cy.json index c32e87b5d..217d152bd 100644 --- a/ui/localization/messages/cy.json +++ b/ui/localization/messages/cy.json @@ -478,7 +478,7 @@ "login_password_label": "Cyfrinair", "login_welcome_back": "Croeso nôl i JetKVM", "macro_add_step": "Ychwanegu Cam{maxed_out}", - "macro_at_least_one_step_keys_or_modifiers": "Rhaid i o leiaf un cam gynnwys bysellau neu addasyddion", + "macro_at_least_one_step_keys_or_modifiers": "Rhaid i o leiaf un cam gynnwys bysellau, addasyddion neu destun", "macro_at_least_one_step_required": "Mae angen o leiaf un cam", "macro_max_steps_error": "Gallwch ychwanegu uchafswm o {max} cam fesul macro yn unig.", "macro_max_steps_reached": "({max} uchafswm)", diff --git a/ui/localization/messages/da.json b/ui/localization/messages/da.json index f9253863e..45a6fe0bb 100644 --- a/ui/localization/messages/da.json +++ b/ui/localization/messages/da.json @@ -483,7 +483,7 @@ "login_password_label": "Adgangskode", "login_welcome_back": "Velkommen tilbage til JetKVM", "macro_add_step": "Tilføj trin {maxed_out}", - "macro_at_least_one_step_keys_or_modifiers": "Mindst ét trin skal have nøgler eller modifikatorer", + "macro_at_least_one_step_keys_or_modifiers": "Mindst ét trin skal have nøgler, modifikatorer eller tekst", "macro_at_least_one_step_required": "Mindst ét trin er påkrævet", "macro_max_steps_error": "Du kan maksimalt tilføje {max} trin pr. makro.", "macro_max_steps_reached": "( {max} maks)", diff --git a/ui/localization/messages/de.json b/ui/localization/messages/de.json index bd26f37ba..c1432974b 100644 --- a/ui/localization/messages/de.json +++ b/ui/localization/messages/de.json @@ -483,7 +483,7 @@ "login_password_label": "Passwort", "login_welcome_back": "Willkommen zurück bei JetKVM", "macro_add_step": "Schritt hinzufügen {maxed_out}", - "macro_at_least_one_step_keys_or_modifiers": "Mindestens ein Schritt muss Schlüssel oder Modifikatoren haben", + "macro_at_least_one_step_keys_or_modifiers": "Mindestens ein Schritt muss Tasten, Modifikatoren oder Text haben", "macro_at_least_one_step_required": "Mindestens ein Schritt ist erforderlich", "macro_max_steps_error": "Sie können pro Makro maximal {max} Schritte hinzufügen.", "macro_max_steps_reached": "( {max} max)", diff --git a/ui/localization/messages/en.json b/ui/localization/messages/en.json index 93004e077..77d455d96 100644 --- a/ui/localization/messages/en.json +++ b/ui/localization/messages/en.json @@ -483,7 +483,7 @@ "login_password_label": "Password", "login_welcome_back": "Welcome back to JetKVM", "macro_add_step": "Add Step{maxed_out}", - "macro_at_least_one_step_keys_or_modifiers": "At least one step must have keys or modifiers", + "macro_at_least_one_step_keys_or_modifiers": "At least one step must have keys, modifiers, or text", "macro_at_least_one_step_required": "At least one step is required", "macro_max_steps_error": "You can only add a maximum of {max} steps per macro.", "macro_max_steps_reached": "({max} max)", @@ -506,6 +506,14 @@ "macro_step_modifiers_label": "Modifiers", "macro_step_no_matching_keys_found": "No matching keys found", "macro_step_search_for_key": "Search for key…", + "macro_step_text_description": "Text to type on the remote machine. Each character will be sent as individual keystrokes.", + "macro_step_text_invalid_chars": "Some characters may not be typeable with the current keyboard layout", + "macro_step_text_label": "Text to Type", + "macro_step_text_placeholder": "Enter text to type…", + "macro_step_type_description": "Choose whether this step sends key presses or types text.", + "macro_step_type_keys": "Keys", + "macro_step_type_label": "Step Type", + "macro_step_type_text": "Type Text", "macro_steps_description": "Keys/modifiers executed in sequence with a delay between each step.", "macro_steps_label": "Steps", "macros_add_description": "Create a new keyboard macro", diff --git a/ui/localization/messages/es.json b/ui/localization/messages/es.json index f75093b05..38bc0a373 100644 --- a/ui/localization/messages/es.json +++ b/ui/localization/messages/es.json @@ -483,7 +483,7 @@ "login_password_label": "Contraseña", "login_welcome_back": "Bienvenido de nuevo a JetKVM", "macro_add_step": "Agregar paso {maxed_out}", - "macro_at_least_one_step_keys_or_modifiers": "Al menos un paso debe tener claves o modificadores", + "macro_at_least_one_step_keys_or_modifiers": "Al menos un paso debe tener teclas, modificadores o texto", "macro_at_least_one_step_required": "Se requiere al menos un paso", "macro_max_steps_error": "Solo puede agregar un máximo de {max} pasos por macro.", "macro_max_steps_reached": "( {max} máx.)", diff --git a/ui/localization/messages/fr.json b/ui/localization/messages/fr.json index 0893d3420..95660eddb 100644 --- a/ui/localization/messages/fr.json +++ b/ui/localization/messages/fr.json @@ -483,7 +483,7 @@ "login_password_label": "Mot de passe", "login_welcome_back": "Bienvenue à JetKVM", "macro_add_step": "Ajouter l'étape {maxed_out}", - "macro_at_least_one_step_keys_or_modifiers": "Au moins une étape doit avoir des clés ou des modificateurs", + "macro_at_least_one_step_keys_or_modifiers": "Au moins une étape doit avoir des touches, des modificateurs ou du texte", "macro_at_least_one_step_required": "Au moins une étape est requise", "macro_max_steps_error": "Vous ne pouvez ajouter qu'un maximum de {max} étapes par macro.", "macro_max_steps_reached": "( {max} max)", diff --git a/ui/localization/messages/it.json b/ui/localization/messages/it.json index 9d551194a..0a55bc6e2 100644 --- a/ui/localization/messages/it.json +++ b/ui/localization/messages/it.json @@ -483,7 +483,7 @@ "login_password_label": "Password", "login_welcome_back": "Bentornati a JetKVM", "macro_add_step": "Aggiungi passaggio {maxed_out}", - "macro_at_least_one_step_keys_or_modifiers": "Almeno un passaggio deve avere chiavi o modificatori", + "macro_at_least_one_step_keys_or_modifiers": "Almeno un passaggio deve avere tasti, modificatori o testo", "macro_at_least_one_step_required": "È richiesto almeno un passaggio", "macro_max_steps_error": "È possibile aggiungere al massimo {max} passaggi per macro.", "macro_max_steps_reached": "( {max} max)", diff --git a/ui/localization/messages/ja.json b/ui/localization/messages/ja.json index 85cc72e2f..8c5583306 100644 --- a/ui/localization/messages/ja.json +++ b/ui/localization/messages/ja.json @@ -483,7 +483,7 @@ "login_password_label": "パスワード", "login_welcome_back": "JetKVMへようこそ", "macro_add_step": "ステップを追加{maxed_out}", - "macro_at_least_one_step_keys_or_modifiers": "少なくとも1つのステップにキーまたは修飾キーが必要です", + "macro_at_least_one_step_keys_or_modifiers": "少なくとも1つのステップにキー、修飾キー、またはテキストが必要です", "macro_at_least_one_step_required": "少なくとも1つのステップが必要です", "macro_max_steps_error": "マクロごとに追加できるのは最大 {max} ステップまでです。", "macro_max_steps_reached": "(最大 {max})", diff --git a/ui/localization/messages/nb.json b/ui/localization/messages/nb.json index 48dc3ec87..d8c1aee85 100644 --- a/ui/localization/messages/nb.json +++ b/ui/localization/messages/nb.json @@ -483,7 +483,7 @@ "login_password_label": "Passord", "login_welcome_back": "Velkommen tilbake til JetKVM", "macro_add_step": "Legg til trinn{maxed_out}", - "macro_at_least_one_step_keys_or_modifiers": "Minst ett trinn må ha nøkler eller modifikatorer", + "macro_at_least_one_step_keys_or_modifiers": "Minst ett trinn må ha taster, modifikatorer eller tekst", "macro_at_least_one_step_required": "Minst ett trinn er nødvendig", "macro_max_steps_error": "Du kan bare legge til maksimalt {max} trinn per makro.", "macro_max_steps_reached": "( {max} maks)", diff --git a/ui/localization/messages/pt.json b/ui/localization/messages/pt.json index ae3c6418e..cbabd9128 100644 --- a/ui/localization/messages/pt.json +++ b/ui/localization/messages/pt.json @@ -483,7 +483,7 @@ "login_password_label": "Senha", "login_welcome_back": "Bem-vindo de volta ao JetKVM", "macro_add_step": "Adicionar Etapa{maxed_out}", - "macro_at_least_one_step_keys_or_modifiers": "Pelo menos uma etapa deve ter teclas ou modificadores", + "macro_at_least_one_step_keys_or_modifiers": "Pelo menos uma etapa deve ter teclas, modificadores ou texto", "macro_at_least_one_step_required": "Pelo menos uma etapa é necessária", "macro_max_steps_error": "Você só pode adicionar no máximo {max} etapas por macro.", "macro_max_steps_reached": "({max} máx.)", diff --git a/ui/localization/messages/ru.json b/ui/localization/messages/ru.json index 54be1a0dc..6b7ca44bd 100644 --- a/ui/localization/messages/ru.json +++ b/ui/localization/messages/ru.json @@ -483,7 +483,7 @@ "login_password_label": "Пароль", "login_welcome_back": "Добро пожаловать обратно в JetKVM", "macro_add_step": "Добавить шаг{maxed_out}", - "macro_at_least_one_step_keys_or_modifiers": "По крайней мере один шаг должен содержать клавиши или модификаторы", + "macro_at_least_one_step_keys_or_modifiers": "По крайней мере один шаг должен содержать клавиши, модификаторы или текст", "macro_at_least_one_step_required": "Требуется хотя бы один шаг", "macro_max_steps_error": "Вы можете добавить максимум {max} шагов на макрос.", "macro_max_steps_reached": "({max} максимум)", diff --git a/ui/localization/messages/sv.json b/ui/localization/messages/sv.json index e59481fa8..b10a2ab0e 100644 --- a/ui/localization/messages/sv.json +++ b/ui/localization/messages/sv.json @@ -483,7 +483,7 @@ "login_password_label": "Lösenord", "login_welcome_back": "Välkommen tillbaka till JetKVM", "macro_add_step": "Lägg till steg {maxed_out}", - "macro_at_least_one_step_keys_or_modifiers": "Minst ett steg måste ha nycklar eller modifierare", + "macro_at_least_one_step_keys_or_modifiers": "Minst ett steg måste ha tangenter, modifierare eller text", "macro_at_least_one_step_required": "Minst ett steg krävs", "macro_max_steps_error": "Du kan bara lägga till maximalt {max} steg per makro.", "macro_max_steps_reached": "( {max} max)", diff --git a/ui/localization/messages/zh-tw.json b/ui/localization/messages/zh-tw.json index 73756f677..98eb6fa83 100644 --- a/ui/localization/messages/zh-tw.json +++ b/ui/localization/messages/zh-tw.json @@ -483,7 +483,7 @@ "login_password_label": "密碼", "login_welcome_back": "歡迎回到 JetKVM", "macro_add_step": "新增步驟{maxed_out}", - "macro_at_least_one_step_keys_or_modifiers": "至少有一個步驟必須包含按鍵或修飾鍵", + "macro_at_least_one_step_keys_or_modifiers": "至少有一個步驟必須包含按鍵、修飾鍵或文字", "macro_at_least_one_step_required": "至少需要一個步驟", "macro_max_steps_error": "每個巨集最多只能新增 {max} 個步驟。", "macro_max_steps_reached": "(上限 {max})", diff --git a/ui/localization/messages/zh.json b/ui/localization/messages/zh.json index 9d5b9c1fa..69800cecf 100644 --- a/ui/localization/messages/zh.json +++ b/ui/localization/messages/zh.json @@ -483,7 +483,7 @@ "login_password_label": "密码", "login_welcome_back": "欢迎回到 JetKVM", "macro_add_step": "添加步骤{maxed_out}", - "macro_at_least_one_step_keys_or_modifiers": "至少一个步骤中必须包含按键或修饰键。", + "macro_at_least_one_step_keys_or_modifiers": "至少一个步骤中必须包含按键、修饰键或文本。", "macro_at_least_one_step_required": "至少需要一个步骤。", "macro_max_steps_error": "每个宏最多只能添加 {max} 个步骤。", "macro_max_steps_reached": "({max} 为上限)", diff --git a/ui/src/components/MacroBar.tsx b/ui/src/components/MacroBar.tsx index 63c99b616..d7c96af01 100644 --- a/ui/src/components/MacroBar.tsx +++ b/ui/src/components/MacroBar.tsx @@ -3,6 +3,7 @@ import { LuCommand } from "react-icons/lu"; import { useMacrosStore } from "@hooks/stores"; import useKeyboard from "@hooks/useKeyboard"; +import useKeyboardLayout from "@hooks/useKeyboardLayout"; import { useJsonRpc } from "@hooks/useJsonRpc"; import { Button } from "@components/Button"; import Container from "@components/Container"; @@ -10,6 +11,7 @@ import Container from "@components/Container"; export default function MacroBar() { const { macros, initialized, loadMacros, setSendFn } = useMacrosStore(); const { executeMacro } = useKeyboard(); + const { selectedKeyboard } = useKeyboardLayout(); const { send } = useJsonRpc(); useEffect(() => { @@ -38,7 +40,7 @@ export default function MacroBar() { size="XS" theme="light" text={macro.name} - onClick={() => executeMacro(macro.steps)} + onClick={() => executeMacro(macro.steps, selectedKeyboard)} /> ))} diff --git a/ui/src/components/MacroForm.tsx b/ui/src/components/MacroForm.tsx index c299e26ac..74369c2be 100644 --- a/ui/src/components/MacroForm.tsx +++ b/ui/src/components/MacroForm.tsx @@ -60,11 +60,14 @@ export function MacroForm({ const steps = macro.steps || []; if (steps.length) { - const hasKeyOrModifier = steps.some( - step => step.keys.length > 0 || step.modifiers.length > 0, + const hasContent = steps.some( + step => + step.keys.length > 0 || + step.modifiers.length > 0 || + (step.text !== undefined && step.text.length > 0), ); - if (!hasKeyOrModifier) { + if (!hasContent) { newErrors.steps = { 0: { keys: m.macro_at_least_one_step_keys_or_modifiers() }, }; @@ -162,6 +165,41 @@ export function MacroForm({ setMacro({ ...macro, steps: newSteps }); }; + const handleTextChange = (stepIndex: number, text: string) => { + const newSteps = [...(macro.steps || [])]; + newSteps[stepIndex].text = text; + setMacro({ ...macro, steps: newSteps }); + + // Clear step errors when text is entered + if (errors.steps?.[stepIndex]?.keys && text.length > 0) { + const newErrors = { ...errors }; + delete newErrors.steps?.[stepIndex].keys; + if (Object.keys(newErrors.steps?.[stepIndex] || {}).length === 0) { + delete newErrors.steps?.[stepIndex]; + } + if (Object.keys(newErrors.steps || {}).length === 0) { + delete newErrors.steps; + } + setErrors(newErrors); + } + }; + + const handleStepTypeChange = (stepIndex: number, type: "keys" | "text") => { + const newSteps = [...(macro.steps || [])]; + if (type === "text") { + newSteps[stepIndex] = { + keys: [], + modifiers: [], + delay: newSteps[stepIndex].delay, + text: newSteps[stepIndex].text ?? "", + }; + } else { + const { text: _, ...rest } = newSteps[stepIndex]; + newSteps[stepIndex] = rest; + } + setMacro({ ...macro, steps: newSteps }); + }; + const handleStepMove = (stepIndex: number, direction: "up" | "down") => { const newSteps = [...(macro.steps || [])]; const newIndex = direction === "up" ? stepIndex - 1 : stepIndex + 1; @@ -231,6 +269,8 @@ export function MacroForm({ keyQuery={keyQueries[stepIndex] || ""} onModifierChange={modifiers => handleModifierChange(stepIndex, modifiers)} onDelayChange={delay => handleDelayChange(stepIndex, delay)} + onTextChange={text => handleTextChange(stepIndex, text)} + onStepTypeChange={type => handleStepTypeChange(stepIndex, type)} isLastStep={stepIndex === (macro.steps?.length || 0) - 1} keyboard={selectedKeyboard} /> diff --git a/ui/src/components/MacroStepCard.tsx b/ui/src/components/MacroStepCard.tsx index ee39c7364..265422fa6 100644 --- a/ui/src/components/MacroStepCard.tsx +++ b/ui/src/components/MacroStepCard.tsx @@ -56,6 +56,7 @@ interface MacroStep { keys: string[]; modifiers: string[]; delay: number; + text?: string; } interface MacroStepCardProps { @@ -69,6 +70,8 @@ interface MacroStepCardProps { keyQuery: string; onModifierChange: (modifiers: string[]) => void; onDelayChange: (delay: number) => void; + onTextChange: (text: string) => void; + onStepTypeChange: (type: "keys" | "text") => void; isLastStep: boolean; keyboard: KeyboardLayout; } @@ -92,11 +95,15 @@ export function MacroStepCard({ keyQuery, onModifierChange, onDelayChange, + onTextChange, + onStepTypeChange, isLastStep, keyboard, }: Readonly) { const { keyDisplayMap } = keyboard; + const isTextMode = step.text !== undefined; + const keyOptions = useMemo( () => Object.keys(keys) @@ -130,6 +137,17 @@ export function MacroStepCard({ } }, [keyOptions, keyQuery, step.keys]); + const invalidChars = useMemo(() => { + if (!isTextMode || !step.text) return []; + return [ + ...new Set( + [...(new Intl.Segmenter().segment(step.text) ?? [])] + .map(x => x.segment.normalize("NFC")) + .filter(char => !keyboard.chars[char]), + ), + ]; + }, [isTextMode, step.text, keyboard.chars]); + return (
@@ -169,92 +187,143 @@ export function MacroStepCard({
-
-
- + +
+
-
- ))} -
+ -
-
+
+ {isTextMode ? ( + /* Type Text Mode */ +
+