abouttreesummaryrefslogcommitdiff
diff options
context:
space:
mode:
authorJules Aguillon2025-02-23 12:12:29 +0100
committerGitHub2025-02-23 12:12:29 +0100
commit68be82a4f92f47300b9960cf9cf65040c96f17ed (patch)
treece79243fe3c1fff7b799af2040a7a76377e4ad5f
parent581b31bf99bf7a4088ef12ea7e03fbc75d5acfed (diff)
downloadunexpected-keyboard-68be82a4f92f47300b9960cf9cf65040c96f17ed.tar.gz
unexpected-keyboard-68be82a4f92f47300b9960cf9cf65040c96f17ed.zip
Macro keys (#878)
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: symbol:key1,key2,.. The symbol cannot contain a : character. 'key1', 'key2', etc.. are: - 'String with \' escaping' The key will generate the specified string. - keyevent:123 The key will send a keyevent. - The name of any special key
-rw-r--r--doc/Possible-key-values.md74
-rw-r--r--srcs/juloo.keyboard2/Autocapitalisation.java18
-rw-r--r--srcs/juloo.keyboard2/KeyEventHandler.java34
-rw-r--r--srcs/juloo.keyboard2/KeyValue.java64
-rw-r--r--srcs/juloo.keyboard2/KeyValueParser.java299
-rw-r--r--srcs/juloo.keyboard2/prefs/CustomExtraKeysPreference.java2
-rw-r--r--test/juloo.keyboard2/KeyValueParserTest.java103
7 files changed, 433 insertions, 161 deletions
diff --git a/doc/Possible-key-values.md b/doc/Possible-key-values.md
index 1c0908f..2073627 100644
--- a/doc/Possible-key-values.md
+++ b/doc/Possible-key-values.md
@@ -1,9 +1,30 @@
# Key values
-This is an exhaustive list of special values accepted for the `key0` through `key8` or `nw` through `se` attributes on a key.
+A key value is the denomination of a key accepted in the "Add keys to the keyboard" option or for the `nw`, ..., `se` attributes in custom layouts (or `key0` ... `key8`).
+It can be:
-Any string that does not exactly match these will be printed verbatim.
-A key can output multiple characters, but cannot combine multiple built-in key values.
+- The name of a special key. An exhaustive list of the special keys follows.
+
+- An arbitrary sequence of characters not containing `:`.
+ This results in a key that writes the specified characters.
+
+- Using the syntax `symbol:key_def`.
+ `symbol` is the symbol that appears on the keyboard, it cannot contain `:`.
+ `key_def` can be:
+ + The name of a special key, as listed below.
+ + `'Arbitrary string'` An arbitrary string that can contain `:`. `'` can be added to the string as `` \' ``.
+ + `keyevent:keycode` An Android keycode. They are listed as `KEYCODE_...` in [KeyEvent](https://developer.android.com/reference/android/view/KeyEvent#summary).
+
+ Examples:
+ + `⏯:keyevent:85` A play/pause key (which probably doesn't do anything in most apps).
+ + `my@:'my.email@domain.com'` An arbitrary string key
+
+- A macro, `symbol:key_def1,key_def2,...`.
+ This results in a key that behaves as if the sequence of `key_def` had been pressed in order.
+
+ Examples:
+ + `CA:ctrl,a,ctrl,c` The sequence `ctrl+a`, `ctrl+c`.
+ + `Cd:ctrl,backspace` The shortcut `ctrl+backspace`.
## Escape codes
Value | Escape code for
@@ -152,50 +173,3 @@ These keys are known to do nothing.
These keys are normally hidden unless the Fn modifier is activated.
`f11_placeholder` | `f12_placeholder`
-
-## Complex keys
-
-More complex keys are of this form:
-
-```
-:<kind> <attributes>:<payload>
-```
-
-Where `<kind>` is one of the kinds documented below and `<attributes>` is a
-space separated list of attributes. `<payload>` depends on the `<kind>`.
-
-Attributes are:
-- `symbol='Sym'` specifies the symbol to be shown on the keyboard.
-- `flags='<flags>'` changes the behavior of the key.
- `<flags>` is a coma separated list of:
- + `dim`: Make the symbol dimmer.
- + `small`: Make the symbol smaller.
-
-### Kind `str`
-
-Defines a key that outputs an arbitrary string. `<payload>` is a string wrapped
-in single-quotes (`'`), escaping of other single quotes is allowed with `\'`.
-
-For example:
-- `:str:'Arbitrary string with a \' inside'`
-- `:str symbol='Symbol':'Output string'`
-
-### Kind `char`
-
-Defines a key that outputs a single character. `<payload>` is the character to
-output, unquoted.
-This kind of key can be used to define a character key with a different symbol
-on it. `char` keys can be modified by `ctrl` and other modifiers, unlike `str`
-keys.
-
-For example:
-- `:char symbol='љ':q`, which is used to implement `ctrl` shortcuts in cyrillic
- layouts.
-
-### Kind `keyevent`
-
-Defines a key that sends an Android [key event](https://developer.android.com/reference/android/view/KeyEvent).
-`<payload>` is the key event number.
-
-For example:
-- `:keyevent symbol='⏯' flags='small':85`
diff --git a/srcs/juloo.keyboard2/Autocapitalisation.java b/srcs/juloo.keyboard2/Autocapitalisation.java
index bf28e9d..a77d2e5 100644
--- a/srcs/juloo.keyboard2/Autocapitalisation.java
+++ b/srcs/juloo.keyboard2/Autocapitalisation.java
@@ -88,6 +88,24 @@ public final class Autocapitalisation
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);
diff --git a/srcs/juloo.keyboard2/KeyEventHandler.java b/srcs/juloo.keyboard2/KeyEventHandler.java
index 6809d88..d2546ab 100644
--- a/srcs/juloo.keyboard2/KeyEventHandler.java
+++ b/srcs/juloo.keyboard2/KeyEventHandler.java
@@ -97,11 +97,10 @@ public final class KeyEventHandler
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);
}
@@ -319,6 +318,35 @@ public final class KeyEventHandler
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)
{
diff --git a/srcs/juloo.keyboard2/KeyValue.java b/srcs/juloo.keyboard2/KeyValue.java
index 60d49e4..b2ef5db 100644
--- a/srcs/juloo.keyboard2/KeyValue.java
+++ b/srcs/juloo.keyboard2/KeyValue.java
@@ -96,6 +96,7 @@ public final class KeyValue implements Comparable<KeyValue>
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 = 20;
@@ -105,7 +106,8 @@ public final class KeyValue implements Comparable<KeyValue>
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.
@@ -229,6 +231,12 @@ public final class KeyValue implements Comparable<KeyValue>
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)
{
@@ -460,31 +468,35 @@ public final class KeyValue implements Comparable<KeyValue>
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)
{
return new KeyValue("", Kind.Modifier, mod.ordinal(), 0);
}
- public static KeyValue parseKeyDefinition(String str)
+ /** Return a key by its name. If the given name doesn't correspond to any
+ special key, it is parsed with [KeyValueParser]. */
+ public static KeyValue getKeyByName(String name)
{
- if (str.length() < 2 || str.charAt(0) != ':')
- return makeStringKey(str);
+ KeyValue k = getSpecialKeyByName(name);
+ if (k != null)
+ return k;
try
{
- return KeyValueParser.parse(str);
+ return KeyValueParser.parse(name);
}
catch (KeyValueParser.ParseError _e)
{
- return makeStringKey(str);
+ return makeStringKey(name);
}
}
- /**
- * Return a key by its name. If the given name doesn't correspond to a key
- * defined in this function, it is passed to [parseStringKey] as a fallback.
- */
- public static KeyValue getKeyByName(String name)
+ public static KeyValue getSpecialKeyByName(String name)
{
switch (name)
{
@@ -735,8 +747,7 @@ public final class KeyValue implements Comparable<KeyValue>
case "௲": case "௳":
return makeStringKey(name, FLAG_SMALLER_FONT);
- /* The key is not one of the special ones. */
- default: return parseKeyDefinition(name);
+ default: return null;
}
}
@@ -787,4 +798,31 @@ public final class KeyValue implements Comparable<KeyValue>
@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; }
+
+ @Override
+ 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);
+ }
+ };
}
diff --git a/srcs/juloo.keyboard2/KeyValueParser.java b/srcs/juloo.keyboard2/KeyValueParser.java
index 0a5ce17..488f5d3 100644
--- a/srcs/juloo.keyboard2/KeyValueParser.java
+++ b/srcs/juloo.keyboard2/KeyValueParser.java
@@ -1,14 +1,22 @@
package juloo.keyboard2;
+import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
Parse a key definition. The syntax for a key definition is:
+- [(symbol):(key_action)]
- [:(kind) (attributes):(payload)].
- If [str] doesn't start with a [:] character, it is interpreted as an
arbitrary string key.
+[key_action] is:
+- ['Arbitrary string']
+- [(key_action),(key_action),...]
+- [keyevent:(code)]
+- [(key_name)]
+
For the different kinds and attributes, see doc/Possible-key-values.md.
Examples:
@@ -18,103 +26,232 @@ Examples:
*/
public final class KeyValueParser
{
- static Pattern START_PAT;
- static Pattern ATTR_PAT;
+ static Pattern KEYDEF_TOKEN;
static Pattern QUOTED_PAT;
- static Pattern PAYLOAD_START_PAT;
static Pattern WORD_PAT;
- static public KeyValue parse(String str) throws ParseError
+ static public KeyValue parse(String input) throws ParseError
{
- String symbol = null;
- int flags = 0;
+ int symbol_ends = 0;
+ final int input_len = input.length();
+ while (symbol_ends < input_len && input.charAt(symbol_ends) != ':')
+ symbol_ends++;
+ if (symbol_ends == 0) // Old syntax
+ return Starting_with_colon.parse(input);
+ if (symbol_ends == input_len) // String key
+ return KeyValue.makeStringKey(input);
+ String symbol = input.substring(0, symbol_ends);
+ ArrayList<KeyValue> keydefs = new ArrayList<KeyValue>();
init();
- // Kind
- Matcher m = START_PAT.matcher(str);
- if (!m.lookingAt())
- parseError("Expected kind, for example \":str ...\".", m);
- String kind = m.group(1);
- // Attributes
- while (true)
- {
- if (!match(m, ATTR_PAT))
- break;
- String attr_name = m.group(1);
- String attr_value = parseSingleQuotedString(m);
- switch (attr_name)
- {
- case "flags":
- flags = parseFlags(attr_value, m);
- break;
- case "symbol":
- symbol = attr_value;
- break;
+ Matcher m = KEYDEF_TOKEN.matcher(input);
+ m.region(symbol_ends + 1, input_len);
+ do { keydefs.add(parse_key_def(m)); }
+ while (parse_comma(m));
+ for (KeyValue k : keydefs)
+ if (k == null)
+ parseError("Contains null key", m);
+ return KeyValue.makeMacro(symbol, keydefs.toArray(new KeyValue[]{}), 0);
+ }
- default:
- parseError("Unknown attribute "+attr_name, m);
- }
- }
- // Payload
- if (!match(m, PAYLOAD_START_PAT))
- parseError("Unexpected character", m);
- String payload;
- switch (kind)
+ static void init()
+ {
+ if (KEYDEF_TOKEN != null)
+ return;
+ KEYDEF_TOKEN = Pattern.compile("'|,|keyevent:|(?:[^\\\\',]+|\\\\.)+");
+ QUOTED_PAT = Pattern.compile("((?:[^'\\\\]+|\\\\')*)'");
+ WORD_PAT = Pattern.compile("[a-zA-Z0-9_]+|.");
+ }
+
+ static KeyValue key_by_name_or_str(String str)
+ {
+ KeyValue k = KeyValue.getSpecialKeyByName(str);
+ if (k != null)
+ return k;
+ return KeyValue.makeStringKey(str);
+ }
+
+ static KeyValue parse_key_def(Matcher m) throws ParseError
+ {
+ if (!match(m, KEYDEF_TOKEN))
+ parseError("Expected key definition", m);
+ String token = m.group(0);
+ switch (token)
{
- case "str":
- payload = parseSingleQuotedString(m);
- if (symbol == null)
- return KeyValue.makeStringKey(payload, flags);
- return KeyValue.makeStringKeyWithSymbol(payload, symbol, flags);
-
- case "char":
- payload = parsePayloadWord(m);
- if (payload.length() != 1)
- parseError("Expected a single character payload", m);
- return KeyValue.makeCharKey(payload.charAt(0), symbol, flags);
-
- case "keyevent":
- payload = parsePayloadWord(m);
- int eventcode = 0;
- try { eventcode = Integer.parseInt(payload); }
- catch (Exception _e)
- { parseError("Expected an integer payload", m); }
- if (symbol == null)
- symbol = String.valueOf(eventcode);
- return KeyValue.keyeventKey(symbol, eventcode, flags);
-
- default: break;
+ case "'": return parse_string_keydef(m);
+ case ",": parseError("Unexpected comma", m); return null;
+ case "keyevent:": return parse_keyevent_keydef(m);
+ default: return key_by_name_or_str(remove_escaping(token));
}
- parseError("Unknown kind '"+kind+"'", m, 1);
- return null; // Unreachable
}
- static String parseSingleQuotedString(Matcher m) throws ParseError
+ static KeyValue parse_string_keydef(Matcher m) throws ParseError
{
if (!match(m, QUOTED_PAT))
- parseError("Expected quoted string", m);
- return m.group(1).replace("\\'", "'");
+ parseError("Unterminated quoted string", m);
+ return KeyValue.makeStringKey(remove_escaping(m.group(1)));
}
- static String parsePayloadWord(Matcher m) throws ParseError
+ static KeyValue parse_keyevent_keydef(Matcher m) throws ParseError
{
if (!match(m, WORD_PAT))
- parseError("Expected a word after ':' made of [a-zA-Z0-9_]", m);
- return m.group(0);
+ parseError("Expected keyevent code", m);
+ int eventcode = 0;
+ try { eventcode = Integer.parseInt(m.group(0)); }
+ catch (Exception _e)
+ { parseError("Expected an integer payload", m); }
+ return KeyValue.keyeventKey("", eventcode, 0);
+ }
+
+ /** Returns [true] if the next token is a comma, [false] if it is the end of the input. Throws an error otherwise. */
+ static boolean parse_comma(Matcher m) throws ParseError
+ {
+ if (!match(m, KEYDEF_TOKEN))
+ return false;
+ String token = m.group(0);
+ if (!token.equals(","))
+ parseError("Expected comma instead of '"+ token + "'", m);
+ return true;
+ }
+
+ static String remove_escaping(String s)
+ {
+ if (!s.contains("\\"))
+ return s;
+ StringBuilder out = new StringBuilder(s.length());
+ final int len = s.length();
+ int prev = 0, i = 0;
+ for (; i < len; i++)
+ if (s.charAt(i) == '\\')
+ {
+ out.append(s, prev, i);
+ prev = i + 1;
+ }
+ out.append(s, prev, i);
+ return out.toString();
}
- static int parseFlags(String s, Matcher m) throws ParseError
+ /**
+ Parse a key definition starting with a [:]. This is the old syntax and is
+ kept for compatibility.
+ */
+ final static class Starting_with_colon
{
- int flags = 0;
- for (String f : s.split(","))
+ static Pattern START_PAT;
+ static Pattern ATTR_PAT;
+ static Pattern QUOTED_PAT;
+ static Pattern PAYLOAD_START_PAT;
+ static Pattern WORD_PAT;
+
+ static public KeyValue parse(String str) throws ParseError
{
- switch (f)
+ 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);
+ // Attributes
+ while (true)
{
- case "dim": flags |= KeyValue.FLAG_SECONDARY; break;
- case "small": flags |= KeyValue.FLAG_SMALLER_FONT; break;
- default: parseError("Unknown flag "+f, m);
+ if (!match(m, ATTR_PAT))
+ break;
+ String attr_name = m.group(1);
+ String attr_value = parseSingleQuotedString(m);
+ switch (attr_name)
+ {
+ case "flags":
+ flags = parseFlags(attr_value, m);
+ break;
+ case "symbol":
+ symbol = attr_value;
+ break;
+
+ default:
+ parseError("Unknown attribute "+attr_name, m);
+ }
}
+ // Payload
+ if (!match(m, PAYLOAD_START_PAT))
+ parseError("Unexpected character", m);
+ String payload;
+ switch (kind)
+ {
+ case "str":
+ payload = parseSingleQuotedString(m);
+ if (symbol == null)
+ return KeyValue.makeStringKey(payload, flags);
+ return KeyValue.makeStringKeyWithSymbol(payload, symbol, flags);
+
+ case "char":
+ payload = parsePayloadWord(m);
+ if (payload.length() != 1)
+ parseError("Expected a single character payload", m);
+ return KeyValue.makeCharKey(payload.charAt(0), symbol, flags);
+
+ case "keyevent":
+ payload = parsePayloadWord(m);
+ int eventcode = 0;
+ try { eventcode = Integer.parseInt(payload); }
+ catch (Exception _e)
+ { parseError("Expected an integer payload", m); }
+ if (symbol == null)
+ symbol = String.valueOf(eventcode);
+ return KeyValue.keyeventKey(symbol, eventcode, flags);
+
+ default: break;
+ }
+ parseError("Unknown kind '"+kind+"'", m, 1);
+ return null; // Unreachable
+ }
+
+ static String parseSingleQuotedString(Matcher m) throws ParseError
+ {
+ if (!match(m, QUOTED_PAT))
+ parseError("Expected quoted string", m);
+ return m.group(1).replace("\\'", "'");
+ }
+
+ static String parsePayloadWord(Matcher m) throws ParseError
+ {
+ if (!match(m, WORD_PAT))
+ parseError("Expected a word after ':' made of [a-zA-Z0-9_]", m);
+ return m.group(0);
+ }
+
+ static int parseFlags(String s, Matcher m) throws ParseError
+ {
+ int flags = 0;
+ for (String f : s.split(","))
+ {
+ switch (f)
+ {
+ case "dim": flags |= KeyValue.FLAG_SECONDARY; break;
+ case "small": flags |= KeyValue.FLAG_SMALLER_FONT; break;
+ default: parseError("Unknown flag "+f, m);
+ }
+ }
+ return flags;
+ }
+
+ 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_]*");
}
- return flags;
}
static boolean match(Matcher m, Pattern pat)
@@ -124,17 +261,6 @@ public final class KeyValueParser
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());
@@ -145,8 +271,7 @@ public final class KeyValueParser
StringBuilder msg_ = new StringBuilder("Syntax error");
try
{
- char c = m.group(0).charAt(0);
- msg_.append(" at character '").append(c).append("'");
+ msg_.append(" at token '").append(m.group(0)).append("'");
} catch (IllegalStateException _e) {}
msg_.append(" at position ");
msg_.append(i);
diff --git a/srcs/juloo.keyboard2/prefs/CustomExtraKeysPreference.java b/srcs/juloo.keyboard2/prefs/CustomExtraKeysPreference.java
index 6f4cf41..253e074 100644
--- a/srcs/juloo.keyboard2/prefs/CustomExtraKeysPreference.java
+++ b/srcs/juloo.keyboard2/prefs/CustomExtraKeysPreference.java
@@ -41,7 +41,7 @@ public class CustomExtraKeysPreference extends ListGroupPreference<String>
if (key_names != null)
{
for (String key_name : key_names)
- kvs.put(KeyValue.parseKeyDefinition(key_name), KeyboardData.PreferredPos.DEFAULT);
+ kvs.put(KeyValue.getKeyByName(key_name), KeyboardData.PreferredPos.DEFAULT);
}
return kvs;
}
diff --git a/test/juloo.keyboard2/KeyValueParserTest.java b/test/juloo.keyboard2/KeyValueParserTest.java
index a636ebf..a041e8a 100644
--- a/test/juloo.keyboard2/KeyValueParserTest.java
+++ b/test/juloo.keyboard2/KeyValueParserTest.java
@@ -10,7 +10,100 @@ public class KeyValueParserTest
public KeyValueParserTest() {}
@Test
- public void parseStr() throws Exception
+ public void parse_key_value() throws Exception
+ {
+ Utils.parse("'", KeyValue.makeStringKey("'"));
+ Utils.parse("\\'", KeyValue.makeStringKey("\\'"));
+ Utils.parse("\\,", KeyValue.makeStringKey("\\,"));
+ Utils.parse("a\\'b", KeyValue.makeStringKey("a\\'b"));
+ Utils.parse("a\\,b", KeyValue.makeStringKey("a\\,b"));
+ Utils.parse("a", KeyValue.makeStringKey("a"));
+ Utils.parse("abc", KeyValue.makeStringKey("abc"));
+ Utils.parse("shift", KeyValue.getSpecialKeyByName("shift"));
+ Utils.parse("'a", KeyValue.makeStringKey("'a"));
+ }
+
+ @Test
+ public void parse_macro() throws Exception
+ {
+ Utils.parse("symbol:abc", KeyValue.makeMacro("symbol", new KeyValue[]{
+ KeyValue.makeStringKey("abc")
+ }, 0));
+ Utils.parse("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.parse("macro:abc,\\,", KeyValue.makeMacro("macro", new KeyValue[]{
+ KeyValue.makeStringKey("abc"),
+ KeyValue.makeStringKey(",")
+ }, 0));
+ Utils.parse("<2:ctrl,backspace", KeyValue.makeMacro("<2", new KeyValue[]{
+ KeyValue.getSpecialKeyByName("ctrl"),
+ KeyValue.getSpecialKeyByName("backspace")
+ }, 0));
+ Utils.expect_error("symbol:");
+ Utils.expect_error("unterminated_string:'");
+ Utils.expect_error("unterminated_string:abc,'");
+ Utils.expect_error("unexpected_quote:abc,,");
+ Utils.expect_error("unexpected_quote:,");
+ }
+
+ @Test
+ public void parse_string_key() throws Exception
+ {
+ Utils.parse("symbol:'str'", KeyValue.makeMacro("symbol", new KeyValue[]{
+ KeyValue.makeStringKey("str")
+ }, 0));
+ Utils.parse("symbol:'str\\''", KeyValue.makeMacro("symbol", new KeyValue[]{
+ KeyValue.makeStringKey("str'")
+ }, 0));
+ Utils.parse("macro:'str',abc", KeyValue.makeMacro("macro", new KeyValue[]{
+ KeyValue.makeStringKey("str"),
+ KeyValue.makeStringKey("abc")
+ }, 0));
+ Utils.parse("macro:abc,'str'", KeyValue.makeMacro("macro", new KeyValue[]{
+ KeyValue.makeStringKey("abc"),
+ KeyValue.makeStringKey("str")
+ }, 0));
+ Utils.parse("macro:\\',\\,", KeyValue.makeMacro("macro", new KeyValue[]{
+ KeyValue.makeStringKey("'"),
+ KeyValue.makeStringKey(","),
+ }, 0));
+ Utils.parse("macro:a\\'b,a\\,b,a\\xb", KeyValue.makeMacro("macro", new KeyValue[]{
+ KeyValue.makeStringKey("a'b"),
+ KeyValue.makeStringKey("a,b"),
+ KeyValue.makeStringKey("axb")
+ }, 0));
+ Utils.expect_error("symbol:'");
+ Utils.expect_error("symbol:'foo");
+ }
+
+ @Test
+ public void parse_key_event() throws Exception
+ {
+ Utils.parse("symbol:keyevent:85", KeyValue.makeMacro("symbol", new KeyValue[]{
+ KeyValue.keyeventKey("", 85, 0)
+ }, 0));
+ Utils.parse("macro:keyevent:85,abc", KeyValue.makeMacro("macro", new KeyValue[]{
+ KeyValue.keyeventKey("", 85, 0),
+ KeyValue.makeStringKey("abc")
+ }, 0));
+ Utils.parse("macro:abc,keyevent:85", KeyValue.makeMacro("macro", new KeyValue[]{
+ KeyValue.makeStringKey("abc"),
+ KeyValue.keyeventKey("", 85, 0)
+ }, 0));
+ Utils.expect_error("symbol:keyevent:");
+ Utils.expect_error("symbol:keyevent:85a");
+ }
+
+ @Test
+ public void parse_old_syntax() throws Exception
{
Utils.parse(":str:'Foo'", KeyValue.makeStringKey("Foo"));
Utils.parse(":str flags='dim':'Foo'", KeyValue.makeStringKey("Foo", KeyValue.FLAG_SECONDARY));
@@ -32,11 +125,7 @@ public class KeyValueParserTest
Utils.expect_error(":str flags='' ");
Utils.expect_error(":str flags='':");
Utils.expect_error(":str flags='':'");
- }
-
- @Test
- public void parseChar() throws Exception
- {
+ // Char
Utils.parse(":char symbol='a':b", KeyValue.makeCharKey('b', "a", 0));
Utils.parse(":char:b", KeyValue.makeCharKey('b', "b", 0));
}
@@ -46,7 +135,7 @@ public class KeyValueParserTest
{
static void parse(String key_descr, KeyValue ref) throws Exception
{
- assertEquals(ref, KeyValueParser.parse(key_descr));
+ assertEquals(ref, KeyValue.getKeyByName(key_descr));
}
static void expect_error(String key_descr)