forked from extern/Unexpected-Keyboard
Compose key
The COMPOSE_PENDING modifier indicate whether a compose sequence is in progress. The new key of kind Compose_pending sets the current state of the sequence. The compose sequences are compiled into a state machine by a python script into a compact encoding. The state of the pending compose is determined by the index of a state.
This commit is contained in:
parent
38deb810f9
commit
8c29073260
15
build.gradle
15
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"
|
||||
|
@ -3,6 +3,6 @@
|
||||
<key width="1.7" key0="ctrl" key1="loc switch_greekmath" key2="loc meta" key4="switch_numeric"/>
|
||||
<key width="1.1" key0="fn" key1="loc alt" key2="loc change_method" key3="switch_emoji" key4="config"/>
|
||||
<key width="4.4" key0="space" key7="switch_forward" key8="switch_backward" key5="cursor_left" key6="cursor_right" slider="true"/>
|
||||
<key width="1.1" key7="up" key6="right" key5="left" key8="down" key1="loc home" key2="loc page_up" key3="loc end" key4="loc page_down"/>
|
||||
<key width="1.1" key0="compose" key7="up" key6="right" key5="left" key8="down" key1="loc home" key2="loc page_up" key3="loc end" key4="loc page_down"/>
|
||||
<key width="1.7" key0="enter" key1="loc voice_typing" key2="action"/>
|
||||
</row>
|
||||
|
80
srcs/compose/compile.py
Normal file
80
srcs/compose/compile.py
Normal file
@ -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)
|
4
srcs/compose/sequences.txt
Normal file
4
srcs/compose/sequences.txt
Normal file
@ -0,0 +1,4 @@
|
||||
=e€
|
||||
`eè
|
||||
`aà
|
||||
`uù
|
55
srcs/juloo.keyboard2/ComposeKey.java
Normal file
55
srcs/juloo.keyboard2/ComposeKey.java
Normal file
@ -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. */
|
||||
}
|
BIN
srcs/juloo.keyboard2/ComposeKeyData.java
Normal file
BIN
srcs/juloo.keyboard2/ComposeKeyData.java
Normal file
Binary file not shown.
@ -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();
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,10 @@ public final class KeyModifier
|
||||
private static HashMap<KeyValue, HashMap<Pointers.Modifiers, KeyValue>> _cache =
|
||||
new HashMap<KeyValue, HashMap<Pointers.Modifiers, KeyValue>>();
|
||||
|
||||
/** 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())
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user