Skip to content

Commit

Permalink
WIP: Macro
Browse files Browse the repository at this point in the history
Add "macro" keys that behave as if a sequence of keys is typed.

Macro can be added to custom layouts or through the "Add keys to the
keyboard option". The syntax is:

    :macro symbol='V':ctrl,v
    :macro symbol='CA':ctrl,a,ctrl,c
    :macro symbol='<<':ctrl,backspace

The key syntax is changed slightly to make it delimited, creating more
precise error messages.
  • Loading branch information
Julow committed Jan 12, 2025
1 parent e3f9341 commit ecd912e
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 23 deletions.
18 changes: 18 additions & 0 deletions srcs/juloo.keyboard2/Autocapitalisation.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,24 @@ public void stop()
callback_now(true);
}

/** Pause auto capitalisation until [unpause()] is called. */
public boolean pause()
{
boolean was_enabled = _enabled;
stop();
_enabled = false;
return was_enabled;
}

/** Continue auto capitalisation after [pause()] was called. Argument is the
output of [pause()]. */
public void unpause(boolean was_enabled)
{
_enabled = was_enabled;
_should_update_caps_mode = true;
callback_now(true);
}

public static interface Callback
{
public void update_shift_state(boolean should_enable, boolean should_disable);
Expand Down
34 changes: 31 additions & 3 deletions srcs/juloo.keyboard2/KeyEventHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,10 @@ public void key_up(KeyValue key, Pointers.Modifiers mods)
case Keyevent: send_key_down_up(key.getKeyevent()); break;
case Modifier: break;
case Editing: handle_editing_key(key.getEditing()); break;
case Compose_pending:
_recv.set_compose_pending(true);
break;
case Compose_pending: _recv.set_compose_pending(true); break;
case Slider: handle_slider(key.getSlider(), key.getSliderRepeat()); break;
case StringWithSymbol: send_text(key.getStringWithSymbol()); break;
case Macro: evaluate_macro(key.getMacro()); break;
}
update_meta_state(old_mods);
}
Expand Down Expand Up @@ -316,6 +315,35 @@ void move_cursor_vertical(int d)
send_key_down_up_repeat(KeyEvent.KEYCODE_DPAD_DOWN, d);
}

void evaluate_macro(KeyValue[] keys)
{
final Pointers.Modifiers empty = Pointers.Modifiers.EMPTY;
// Ignore modifiers that are activated at the time the macro is evaluated
mods_changed(empty);
Pointers.Modifiers mods = empty;
final boolean autocap_paused = _autocap.pause();
for (KeyValue kv : keys)
{
kv = KeyModifier.modify(kv, mods);
if (kv == null)
continue;
if (kv.hasFlagsAny(KeyValue.FLAG_LATCH))
{
// Non-special latchable keys clear latched modifiers
if (!kv.hasFlagsAny(KeyValue.FLAG_SPECIAL))
mods = empty;
mods = mods.with_extra_mod(kv);
}
else
{
key_down(kv, false);
key_up(kv, mods);
mods = empty;
}
}
_autocap.unpause(autocap_paused);
}

/** Repeat calls to [send_key_down_up]. */
void send_key_down_up_repeat(int event_code, int repeat)
{
Expand Down
52 changes: 49 additions & 3 deletions srcs/juloo.keyboard2/KeyValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ public static enum Kind
String, // [_payload] is also the string to output, value is unused.
Slider, // [_payload] is a [KeyValue.Slider], value is slider repeatition.
StringWithSymbol, // [_payload] is a [KeyValue.StringWithSymbol], value is unused.
Macro, // [_payload] is a [KeyValue.Macro], value is unused.
}

private static final int FLAGS_OFFSET = 19;
Expand All @@ -104,7 +105,8 @@ public static enum Kind
public static final int FLAG_LATCH = (1 << FLAGS_OFFSET << 0);
// Key can be locked by typing twice when enabled in settings
public static final int FLAG_DOUBLE_TAP_LOCK = (1 << FLAGS_OFFSET << 1);
// Special keys are not repeated and don't clear latched modifiers.
// Special keys are not repeated.
// Special latchable keys don't clear latched modifiers.
public static final int FLAG_SPECIAL = (1 << FLAGS_OFFSET << 2);
// Whether the symbol should be greyed out. For example, keys that are not
// part of the pending compose sequence.
Expand Down Expand Up @@ -229,6 +231,12 @@ public String getStringWithSymbol()
return ((StringWithSymbol)_payload).str;
}

/** Defined only when [getKind() == Kind.Macro]. */
public KeyValue[] getMacro()
{
return ((Macro)_payload).keys;
}

/* Update the char and the symbol. */
public KeyValue withChar(char c)
{
Expand Down Expand Up @@ -454,6 +462,11 @@ public static KeyValue makeStringKeyWithSymbol(String str, String symbol, int fl
Kind.StringWithSymbol, 0, flags);
}

public static KeyValue makeMacro(String symbol, KeyValue[] keys, int flags)
{
return new KeyValue(new Macro(keys, symbol), Kind.Macro, 0, flags);
}

/** Make a modifier key for passing to [KeyModifier]. */
public static KeyValue makeInternalModifier(Modifier mod)
{
Expand All @@ -479,6 +492,14 @@ public static KeyValue parseKeyDefinition(String str)
* defined in this function, it is passed to [parseStringKey] as a fallback.
*/
public static KeyValue getKeyByName(String name)
{
KeyValue k = getSpecialKeyByName(name);
if (k == null)
return parseKeyDefinition(name);
return k;
}

public static KeyValue getSpecialKeyByName(String name)
{
switch (name)
{
Expand Down Expand Up @@ -728,8 +749,7 @@ public static KeyValue getKeyByName(String name)
case "௲": case "௳":
return makeStringKey(name, FLAG_SMALLER_FONT);

/* The key is not one of the special ones. */
default: return parseKeyDefinition(name);
default: return null;
}
}

Expand Down Expand Up @@ -780,4 +800,30 @@ public static enum Slider
@Override
public String toString() { return symbol; }
};

public static final class Macro implements Comparable<Macro>
{
public final KeyValue[] keys;
private final String _symbol;

public Macro(KeyValue[] keys_, String sym_)
{
keys = keys_;
_symbol = sym_;
}

public String toString() { return _symbol; }

public int compareTo(Macro snd)
{
int d = keys.length - snd.keys.length;
if (d != 0) return d;
for (int i = 0; i < keys.length; i++)
{
d = keys[i].compareTo(snd.keys[i]);
if (d != 0) return d;
}
return _symbol.compareTo(snd._symbol);
}
};
}
75 changes: 58 additions & 17 deletions srcs/juloo.keyboard2/KeyValueParser.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package juloo.keyboard2;

import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand All @@ -23,18 +24,51 @@ public final class KeyValueParser
static Pattern QUOTED_PAT;
static Pattern PAYLOAD_START_PAT;
static Pattern WORD_PAT;
static Pattern COMMA_PAT;
static Pattern END_OF_INPUT_PAT;

static public KeyValue parse(String str) throws ParseError
{
String symbol = null;
int flags = 0;
init();
// Kind
Matcher m = START_PAT.matcher(str);
if (!m.lookingAt())
parseError("Expected kind, for example \":str ...\".", m);
String kind = m.group(1);
KeyValue k = parseKeyValue(m);
if (!match(m, END_OF_INPUT_PAT))
parseError("Unexpected character", m);
return k;
}

static void init()
{
if (START_PAT != null)
return;
START_PAT = Pattern.compile(":(\\w+)");
ATTR_PAT = Pattern.compile("\\s*(\\w+)\\s*=");
QUOTED_PAT = Pattern.compile("'(([^'\\\\]+|\\\\')*)'");
PAYLOAD_START_PAT = Pattern.compile("\\s*:");
WORD_PAT = Pattern.compile("[a-zA-Z0-9_]+|.");
COMMA_PAT = Pattern.compile(",");
END_OF_INPUT_PAT = Pattern.compile("$");
}

static KeyValue parseKeyValue(Matcher m) throws ParseError
{
if (match(m, START_PAT))
return parseComplexKeyValue(m, m.group(1));
// Key doesn't start with ':', accept either a char key or a key name.
if (!match(m, WORD_PAT))
parseError("Expected key, for example \":str ...\".", m);
String key = m.group(0);
KeyValue k = KeyValue.getSpecialKeyByName(key);
if (k == null)
return KeyValue.makeStringKey(key);
return k;
}

static KeyValue parseComplexKeyValue(Matcher m, String kind) throws ParseError
{
// Attributes
String symbol = null;
int flags = 0;
while (true)
{
if (!match(m, ATTR_PAT))
Expand Down Expand Up @@ -82,6 +116,14 @@ static public KeyValue parse(String str) throws ParseError
symbol = String.valueOf(eventcode);
return KeyValue.keyeventKey(symbol, eventcode, flags);

case "macro":
// :macro symbol='copy':ctrl,a,ctrl,c
// :macro symbol='acute':compose,'
KeyValue[] macro = parseKeyValueList(m);
if (symbol == null)
symbol = "macro";
return KeyValue.makeMacro(symbol, macro, flags);

default: break;
}
parseError("Unknown kind '"+kind+"'", m, 1);
Expand Down Expand Up @@ -117,24 +159,23 @@ static int parseFlags(String s, Matcher m) throws ParseError
return flags;
}

// Parse keys separated by comas
static KeyValue[] parseKeyValueList(Matcher m) throws ParseError
{
ArrayList<KeyValue> out = new ArrayList<KeyValue>();
out.add(parseKeyValue(m));
while (match(m, COMMA_PAT))
out.add(parseKeyValue(m));
return out.toArray(new KeyValue[]{});
}

static boolean match(Matcher m, Pattern pat)
{
try { m.region(m.end(), m.regionEnd()); } catch (Exception _e) {}
m.usePattern(pat);
return m.lookingAt();
}

static void init()
{
if (START_PAT != null)
return;
START_PAT = Pattern.compile(":(\\w+)");
ATTR_PAT = Pattern.compile("\\s*(\\w+)\\s*=");
QUOTED_PAT = Pattern.compile("'(([^'\\\\]+|\\\\')*)'");
PAYLOAD_START_PAT = Pattern.compile("\\s*:");
WORD_PAT = Pattern.compile("[a-zA-Z0-9_]*");
}

static void parseError(String msg, Matcher m) throws ParseError
{
parseError(msg, m, m.regionStart());
Expand Down
26 changes: 26 additions & 0 deletions test/juloo.keyboard2/KeyValueParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,32 @@ public void parseChar() throws Exception
Utils.parse(":char:b", KeyValue.makeCharKey('b', "b", 0));
}

@Test
public void parseKeyValue() throws Exception
{
Utils.parse("\'", KeyValue.makeStringKey("\'"));
Utils.parse("a", KeyValue.makeStringKey("a"));
Utils.parse("abc", KeyValue.makeStringKey("abc"));
Utils.parse("shift", KeyValue.getSpecialKeyByName("shift"));
Utils.expect_error("\'a");
}

@Test
public void parseMacro() throws Exception
{
Utils.parse(":macro symbol='copy':ctrl,a,ctrl,c", KeyValue.makeMacro("copy", new KeyValue[]{
KeyValue.getSpecialKeyByName("ctrl"),
KeyValue.makeStringKey("a"),
KeyValue.getSpecialKeyByName("ctrl"),
KeyValue.makeStringKey("c")
}, 0));
Utils.parse(":macro:abc,'", KeyValue.makeMacro("macro", new KeyValue[]{
KeyValue.makeStringKey("abc"),
KeyValue.makeStringKey("'")
}, 0));
Utils.expect_error(":macro:");
}

/** JUnit removes these functions from stacktraces. */
static class Utils
{
Expand Down

0 comments on commit ecd912e

Please sign in to comment.