diff --git a/build.gradle b/build.gradle
index 139b27b..6544bb5 100644
--- a/build.gradle
+++ b/build.gradle
@@ -85,7 +85,6 @@ android {
}
dependencies {
-
}
tasks.register('buildKeyboardFont') {
@@ -108,6 +107,7 @@ tasks.withType(Test).configureEach {
dependsOn 'genLayoutsList'
dependsOn 'checkKeyboardLayouts'
dependsOn 'syncTranslations'
+ dependsOn 'compileComposeSequences'
}
tasks.register('genLayoutsList') {
@@ -138,6 +138,19 @@ tasks.register('syncTranslations') {
}
}
+tasks.register('compileComposeSequences') {
+ def out = "srcs/juloo.keyboard2/ComposeKeyData.java"
+ println "\nGenerating ${out}"
+ exec {
+ def sequences = new File(projectDir, "srcs/compose").listFiles().findAll {
+ it.name.endsWith(".txt")
+ }
+ workingDir = projectDir
+ commandLine("python", "srcs/compose/compile.py", *sequences)
+ standardOutput = new FileOutputStream("${projectDir}/${out}")
+ }
+}
+
tasks.named("preBuild") {
dependsOn += "initDebugKeystore"
dependsOn += "copyRawQwertyUS"
diff --git a/res/xml/bottom_row.xml b/res/xml/bottom_row.xml
index 33e381d..d0562d1 100644
--- a/res/xml/bottom_row.xml
+++ b/res/xml/bottom_row.xml
@@ -3,6 +3,6 @@
-
+
diff --git a/srcs/compose/compile.py b/srcs/compose/compile.py
new file mode 100644
index 0000000..214d4b8
--- /dev/null
+++ b/srcs/compose/compile.py
@@ -0,0 +1,80 @@
+import textwrap, sys
+
+def parse_sequences_file(fname):
+ with open(fname, "r") as inp:
+ return [ (s[:-2], s[-2]) for s in inp if len(s) > 1 ]
+
+# Turn a list of sequences into a trie.
+def add_sequences_to_trie(seqs, trie):
+ for seq, result in seqs:
+ t_ = trie
+ i = 0
+ while i < len(seq) - 1:
+ c = seq[i]
+ if c not in t_:
+ t_[c] = {}
+ t_ = t_[c]
+ i += 1
+ c = seq[i]
+ t_[c] = result
+
+# Compile the trie into a state machine.
+def make_automata(tree_root):
+ states = []
+ def add_tree(t):
+ # Index and size of the new node
+ i = len(states)
+ s = len(t.keys())
+ # Add node header
+ states.append((0, s + 1))
+ i += 1
+ # Reserve space for the current node in both arrays
+ for c in range(s):
+ states.append((None, None))
+ # Add nested nodes and fill the current node
+ for c in sorted(t.keys()):
+ node_i = len(states)
+ add_node(t[c])
+ states[i] = (c, node_i)
+ i += 1
+ def add_leaf(c):
+ states.append((c, 1))
+ def add_node(n):
+ if type(n) == str:
+ add_leaf(n)
+ else:
+ add_tree(n)
+ add_tree(tree_root)
+ return states
+
+# Print the state machine compiled by make_automata into java code that can be
+# used by [ComposeKeyData.java].
+def gen_java(machine):
+ def gen_array(array, indent):
+ return textwrap.fill(", ".join(map(str, array)), subsequent_indent=indent)
+ print("""package juloo.keyboard2;
+
+/** This file is generated, see [srcs/compose/compile.py]. */
+
+public final class ComposeKeyData
+{
+ public static final char[] states = {
+ %s
+ };
+
+ public static final short[] edges = {
+ %s
+ };
+}""" % (
+ gen_array(map(lambda s: repr(s[0]), machine), ' '),
+ gen_array(map(lambda s: s[1], machine), ' '),
+))
+
+total_sequences = 0
+trie = {}
+for fname in sys.argv[1:]:
+ sequences = parse_sequences_file(fname)
+ add_sequences_to_trie(sequences, trie)
+ total_sequences += len(sequences)
+gen_java(make_automata(trie))
+print("Compiled %d sequences" % total_sequences, file=sys.stderr)
diff --git a/srcs/compose/sequences.txt b/srcs/compose/sequences.txt
new file mode 100644
index 0000000..31063b7
--- /dev/null
+++ b/srcs/compose/sequences.txt
@@ -0,0 +1,4 @@
+=e€
+`eè
+`aà
+`uù
diff --git a/srcs/juloo.keyboard2/ComposeKey.java b/srcs/juloo.keyboard2/ComposeKey.java
new file mode 100644
index 0000000..5b89560
--- /dev/null
+++ b/srcs/juloo.keyboard2/ComposeKey.java
@@ -0,0 +1,55 @@
+package juloo.keyboard2;
+
+import java.util.Arrays;
+
+public final class ComposeKey
+{
+ /** Apply the pending compose sequence to [kv]. Returns [null] if [kv] is not
+ part of the pending sequence. */
+ public static KeyValue apply(int state, KeyValue kv)
+ {
+ switch (kv.getKind())
+ {
+ case Char: return apply(state, kv.getChar());
+ /* These keys must not be removed. */
+ case Event: return kv;
+ case Modifier: return kv;
+ /* These keys cannot be part of sequences. */
+ case String: return null;
+ case Keyevent: return null;
+ case Editing: return null;
+ case Placeholder: return null;
+ case Compose_pending: return null;
+ }
+ return null;
+ }
+
+ /** Apply the pending compose sequence to char [c]. */
+ static KeyValue apply(int state, char c)
+ {
+ char[] states = ComposeKeyData.states;
+ short[] edges = ComposeKeyData.edges;
+ int length = edges[state];
+ int next = Arrays.binarySearch(states, state + 1, state + length, c);
+ if (next < 0)
+ return null;
+ next = edges[next];
+ // The next state is the end of a sequence, show the result.
+ if (edges[next] == 1)
+ return KeyValue.makeCharKey(states[next]);
+ return KeyValue.makeComposePending(String.valueOf(c), next, 0);
+ }
+
+ /** The [states] array represents the different states and their transition.
+ A state occupies one or several cells of the array:
+ - The first cell is the result of the conpose sequence if the state is of
+ size 1, [0] otherwise.
+ - The remaining cells are the transitions, sorted alphabetically.
+
+ The [edges] array represents the transition state corresponding to each
+ accepted inputs.
+ Id [states[i]] is the first cell of a state, [edges[i]] is the number of
+ cells occupied by the state [i].
+ If [states[i]] is a transition, [edges[i]] is the index of the state to
+ jump into. */
+}
diff --git a/srcs/juloo.keyboard2/ComposeKeyData.java b/srcs/juloo.keyboard2/ComposeKeyData.java
new file mode 100644
index 0000000..b73db60
Binary files /dev/null and b/srcs/juloo.keyboard2/ComposeKeyData.java differ
diff --git a/srcs/juloo.keyboard2/KeyEventHandler.java b/srcs/juloo.keyboard2/KeyEventHandler.java
index 65de779..6cc7171 100644
--- a/srcs/juloo.keyboard2/KeyEventHandler.java
+++ b/srcs/juloo.keyboard2/KeyEventHandler.java
@@ -69,6 +69,9 @@ public final class KeyEventHandler implements Config.IKeyEventHandler
case META:
_autocap.stop();
break;
+ case COMPOSE_PENDING:
+ KeyModifier.set_compose_pending(0);
+ break;
}
break;
default: break;
@@ -91,6 +94,10 @@ public final class KeyEventHandler implements Config.IKeyEventHandler
case Keyevent: send_key_down_up(key.getKeyevent()); break;
case Modifier: break;
case Editing: handle_editing_key(key.getEditing()); break;
+ case Compose_pending:
+ KeyModifier.set_compose_pending(key.getPendingCompose());
+ _recv.set_compose_pending(true);
+ break;
}
update_meta_state(old_mods);
}
@@ -293,6 +300,7 @@ public final class KeyEventHandler implements Config.IKeyEventHandler
{
public void handle_event_key(KeyValue.Event ev);
public void set_shift_state(boolean state, boolean lock);
+ public void set_compose_pending(boolean pending);
public InputConnection getCurrentInputConnection();
}
diff --git a/srcs/juloo.keyboard2/KeyModifier.java b/srcs/juloo.keyboard2/KeyModifier.java
index 51e7051..94f096b 100644
--- a/srcs/juloo.keyboard2/KeyModifier.java
+++ b/srcs/juloo.keyboard2/KeyModifier.java
@@ -10,6 +10,10 @@ public final class KeyModifier
private static HashMap> _cache =
new HashMap>();
+ /** The current compose state. Whether a compose is pending is signaled by
+ the [COMPOSE_PENDING] modifier. */
+ static int _compose_pending = -1;
+
/** Modify a key according to modifiers. */
public static KeyValue modify(KeyValue k, Pointers.Modifiers mods)
{
@@ -27,7 +31,11 @@ public final class KeyModifier
ks.put(mods, r);
}
/* Keys with an empty string are placeholder keys. */
- return (r.getString().length() == 0) ? null : r;
+ if (r.getString().length() == 0)
+ return null;
+ if (mods.has(KeyValue.Modifier.COMPOSE_PENDING))
+ r = ComposeKey.apply(_compose_pending, r);
+ return r;
}
public static KeyValue modify(KeyValue k, KeyValue.Modifier mod)
@@ -99,6 +107,11 @@ public final class KeyModifier
}
}
+ public static void set_compose_pending(int state)
+ {
+ _compose_pending = state;
+ }
+
private static KeyValue apply_map_char(KeyValue k, Map_char map)
{
switch (k.getKind())
diff --git a/srcs/juloo.keyboard2/KeyValue.java b/srcs/juloo.keyboard2/KeyValue.java
index dbd9e3e..e60e554 100644
--- a/srcs/juloo.keyboard2/KeyValue.java
+++ b/srcs/juloo.keyboard2/KeyValue.java
@@ -26,6 +26,7 @@ public final class KeyValue
// Must be evaluated in the reverse order of their values.
public static enum Modifier
{
+ COMPOSE_PENDING,
SHIFT,
CTRL,
ALT,
@@ -88,7 +89,8 @@ public final class KeyValue
public static enum Kind
{
- Char, String, Keyevent, Event, Modifier, Editing, Placeholder
+ Char, String, Keyevent, Event, Modifier, Editing, Placeholder,
+ Compose_pending
}
// Behavior flags.
@@ -172,11 +174,18 @@ public final class KeyValue
return Editing.values()[(_code & VALUE_BITS)];
}
+ /** Defined only when [getKind() == Kind.Placeholder]. */
public Placeholder getPlaceholder()
{
return Placeholder.values()[(_code & VALUE_BITS)];
}
+ /** Defined only when [getKind() == Kind.Compose_pending]. */
+ public int getPendingCompose()
+ {
+ return (_code & VALUE_BITS);
+ }
+
/* Update the char and the symbol. */
public KeyValue withChar(char c)
{
@@ -303,6 +312,17 @@ public final class KeyValue
return makeStringKey(str, 0);
}
+ public static KeyValue makeCharKey(char c)
+ {
+ return new KeyValue(String.valueOf(c), Kind.Char, c, 0);
+ }
+
+ public static KeyValue makeComposePending(String symbol, int state, int flags)
+ {
+ return new KeyValue(symbol, Kind.Compose_pending, state,
+ flags | FLAG_SPECIAL);
+ }
+
/** Make a key that types a string. A char key is returned for a string of
length 1. */
public static KeyValue makeStringKey(String str, int flags)
@@ -464,6 +484,9 @@ public final class KeyValue
case "textAssist": return editingKey(0xE038, Editing.ASSIST);
case "autofill": return editingKey("auto", Editing.AUTOFILL);
+ /* The compose key */
+ case "compose": return modifierKey("comp", Modifier.COMPOSE_PENDING, FLAG_SECONDARY);
+
/* Placeholder keys */
case "removed": return placeholderKey(Placeholder.REMOVED);
case "f11_placeholder": return placeholderKey(Placeholder.F11);
diff --git a/srcs/juloo.keyboard2/Keyboard2.java b/srcs/juloo.keyboard2/Keyboard2.java
index 2a6025b..e25cd34 100644
--- a/srcs/juloo.keyboard2/Keyboard2.java
+++ b/srcs/juloo.keyboard2/Keyboard2.java
@@ -440,6 +440,11 @@ public class Keyboard2 extends InputMethodService
_keyboardView.set_shift_state(state, lock);
}
+ public void set_compose_pending(boolean pending)
+ {
+ _keyboardView.set_compose_pending(pending);
+ }
+
public InputConnection getCurrentInputConnection()
{
return Keyboard2.this.getCurrentInputConnection();
diff --git a/srcs/juloo.keyboard2/Keyboard2View.java b/srcs/juloo.keyboard2/Keyboard2View.java
index fb95412..88b718c 100644
--- a/srcs/juloo.keyboard2/Keyboard2View.java
+++ b/srcs/juloo.keyboard2/Keyboard2View.java
@@ -25,6 +25,10 @@ public class Keyboard2View extends View
private KeyValue _shift_kv;
private KeyboardData.Key _shift_key;
+ /** Used to add fake pointers. */
+ private KeyValue _compose_kv;
+ private KeyboardData.Key _compose_key;
+
private Pointers _pointers;
private Pointers.Modifiers _mods;
@@ -98,6 +102,8 @@ public class Keyboard2View extends View
_shift_kv = _shift_kv.withFlags(_shift_kv.getFlags() | KeyValue.FLAG_LOCK);
_shift_key = _keyboard.findKeyWithValue(_shift_kv);
}
+ _compose_kv = KeyValue.getKeyByName("compose");
+ _compose_key = _keyboard.findKeyWithValue(_compose_kv);
reset();
}
@@ -109,26 +115,38 @@ public class Keyboard2View extends View
invalidate();
}
- /** Called by auto-capitalisation. */
- public void set_shift_state(boolean state, boolean lock)
+ void set_fake_ptr_latched(KeyboardData.Key key, KeyValue kv, boolean latched,
+ boolean lock)
{
- if (_keyboard == null || _shift_key == null)
+ if (_keyboard == null || key == null)
return;
- int flags = _pointers.getKeyFlags(_shift_key, _shift_kv);
- if (state)
+ int flags = _pointers.getKeyFlags(key, kv);
+ if (latched)
{
if (flags != -1 && !lock)
return; // Don't replace an existing pointer
- _pointers.add_fake_pointer(_shift_kv, _shift_key, lock);
+ _pointers.add_fake_pointer(kv, key, lock);
}
else
{
if ((flags & KeyValue.FLAG_FAKE_PTR) == 0)
return; // Don't remove locked pointers
- _pointers.remove_fake_pointer(_shift_kv, _shift_key);
+ _pointers.remove_fake_pointer(kv, key);
}
}
+ /** Called by auto-capitalisation. */
+ public void set_shift_state(boolean latched, boolean lock)
+ {
+ set_fake_ptr_latched(_shift_key, _shift_kv, latched, lock);
+ }
+
+ /** Called from [KeyEventHandler]. */
+ public void set_compose_pending(boolean pending)
+ {
+ set_fake_ptr_latched(_compose_key, _compose_kv, pending, false);
+ }
+
public KeyValue modifyKey(KeyValue k, Pointers.Modifiers mods)
{
if (_keyboard.modmap != null)