mirror of
https://github.com/Julow/Unexpected-Keyboard.git
synced 2024-11-22 07:13:13 +01:00
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 {
|
dependencies {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('buildKeyboardFont') {
|
tasks.register('buildKeyboardFont') {
|
||||||
@ -108,6 +107,7 @@ tasks.withType(Test).configureEach {
|
|||||||
dependsOn 'genLayoutsList'
|
dependsOn 'genLayoutsList'
|
||||||
dependsOn 'checkKeyboardLayouts'
|
dependsOn 'checkKeyboardLayouts'
|
||||||
dependsOn 'syncTranslations'
|
dependsOn 'syncTranslations'
|
||||||
|
dependsOn 'compileComposeSequences'
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('genLayoutsList') {
|
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") {
|
tasks.named("preBuild") {
|
||||||
dependsOn += "initDebugKeystore"
|
dependsOn += "initDebugKeystore"
|
||||||
dependsOn += "copyRawQwertyUS"
|
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.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="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="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"/>
|
<key width="1.7" key0="enter" key1="loc voice_typing" key2="action"/>
|
||||||
</row>
|
</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:
|
case META:
|
||||||
_autocap.stop();
|
_autocap.stop();
|
||||||
break;
|
break;
|
||||||
|
case COMPOSE_PENDING:
|
||||||
|
KeyModifier.set_compose_pending(0);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default: break;
|
default: break;
|
||||||
@ -91,6 +94,10 @@ public final class KeyEventHandler implements Config.IKeyEventHandler
|
|||||||
case Keyevent: send_key_down_up(key.getKeyevent()); break;
|
case Keyevent: send_key_down_up(key.getKeyevent()); break;
|
||||||
case Modifier: break;
|
case Modifier: break;
|
||||||
case Editing: handle_editing_key(key.getEditing()); 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);
|
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 handle_event_key(KeyValue.Event ev);
|
||||||
public void set_shift_state(boolean state, boolean lock);
|
public void set_shift_state(boolean state, boolean lock);
|
||||||
|
public void set_compose_pending(boolean pending);
|
||||||
public InputConnection getCurrentInputConnection();
|
public InputConnection getCurrentInputConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,10 @@ public final class KeyModifier
|
|||||||
private static HashMap<KeyValue, HashMap<Pointers.Modifiers, KeyValue>> _cache =
|
private static HashMap<KeyValue, HashMap<Pointers.Modifiers, KeyValue>> _cache =
|
||||||
new HashMap<KeyValue, HashMap<Pointers.Modifiers, KeyValue>>();
|
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. */
|
/** Modify a key according to modifiers. */
|
||||||
public static KeyValue modify(KeyValue k, Pointers.Modifiers mods)
|
public static KeyValue modify(KeyValue k, Pointers.Modifiers mods)
|
||||||
{
|
{
|
||||||
@ -27,7 +31,11 @@ public final class KeyModifier
|
|||||||
ks.put(mods, r);
|
ks.put(mods, r);
|
||||||
}
|
}
|
||||||
/* Keys with an empty string are placeholder keys. */
|
/* 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)
|
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)
|
private static KeyValue apply_map_char(KeyValue k, Map_char map)
|
||||||
{
|
{
|
||||||
switch (k.getKind())
|
switch (k.getKind())
|
||||||
|
@ -26,6 +26,7 @@ public final class KeyValue
|
|||||||
// Must be evaluated in the reverse order of their values.
|
// Must be evaluated in the reverse order of their values.
|
||||||
public static enum Modifier
|
public static enum Modifier
|
||||||
{
|
{
|
||||||
|
COMPOSE_PENDING,
|
||||||
SHIFT,
|
SHIFT,
|
||||||
CTRL,
|
CTRL,
|
||||||
ALT,
|
ALT,
|
||||||
@ -88,7 +89,8 @@ public final class KeyValue
|
|||||||
|
|
||||||
public static enum Kind
|
public static enum Kind
|
||||||
{
|
{
|
||||||
Char, String, Keyevent, Event, Modifier, Editing, Placeholder
|
Char, String, Keyevent, Event, Modifier, Editing, Placeholder,
|
||||||
|
Compose_pending
|
||||||
}
|
}
|
||||||
|
|
||||||
// Behavior flags.
|
// Behavior flags.
|
||||||
@ -172,11 +174,18 @@ public final class KeyValue
|
|||||||
return Editing.values()[(_code & VALUE_BITS)];
|
return Editing.values()[(_code & VALUE_BITS)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Defined only when [getKind() == Kind.Placeholder]. */
|
||||||
public Placeholder getPlaceholder()
|
public Placeholder getPlaceholder()
|
||||||
{
|
{
|
||||||
return Placeholder.values()[(_code & VALUE_BITS)];
|
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. */
|
/* Update the char and the symbol. */
|
||||||
public KeyValue withChar(char c)
|
public KeyValue withChar(char c)
|
||||||
{
|
{
|
||||||
@ -303,6 +312,17 @@ public final class KeyValue
|
|||||||
return makeStringKey(str, 0);
|
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
|
/** Make a key that types a string. A char key is returned for a string of
|
||||||
length 1. */
|
length 1. */
|
||||||
public static KeyValue makeStringKey(String str, int flags)
|
public static KeyValue makeStringKey(String str, int flags)
|
||||||
@ -464,6 +484,9 @@ public final class KeyValue
|
|||||||
case "textAssist": return editingKey(0xE038, Editing.ASSIST);
|
case "textAssist": return editingKey(0xE038, Editing.ASSIST);
|
||||||
case "autofill": return editingKey("auto", Editing.AUTOFILL);
|
case "autofill": return editingKey("auto", Editing.AUTOFILL);
|
||||||
|
|
||||||
|
/* The compose key */
|
||||||
|
case "compose": return modifierKey("comp", Modifier.COMPOSE_PENDING, FLAG_SECONDARY);
|
||||||
|
|
||||||
/* Placeholder keys */
|
/* Placeholder keys */
|
||||||
case "removed": return placeholderKey(Placeholder.REMOVED);
|
case "removed": return placeholderKey(Placeholder.REMOVED);
|
||||||
case "f11_placeholder": return placeholderKey(Placeholder.F11);
|
case "f11_placeholder": return placeholderKey(Placeholder.F11);
|
||||||
|
@ -440,6 +440,11 @@ public class Keyboard2 extends InputMethodService
|
|||||||
_keyboardView.set_shift_state(state, lock);
|
_keyboardView.set_shift_state(state, lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void set_compose_pending(boolean pending)
|
||||||
|
{
|
||||||
|
_keyboardView.set_compose_pending(pending);
|
||||||
|
}
|
||||||
|
|
||||||
public InputConnection getCurrentInputConnection()
|
public InputConnection getCurrentInputConnection()
|
||||||
{
|
{
|
||||||
return Keyboard2.this.getCurrentInputConnection();
|
return Keyboard2.this.getCurrentInputConnection();
|
||||||
|
@ -25,6 +25,10 @@ public class Keyboard2View extends View
|
|||||||
private KeyValue _shift_kv;
|
private KeyValue _shift_kv;
|
||||||
private KeyboardData.Key _shift_key;
|
private KeyboardData.Key _shift_key;
|
||||||
|
|
||||||
|
/** Used to add fake pointers. */
|
||||||
|
private KeyValue _compose_kv;
|
||||||
|
private KeyboardData.Key _compose_key;
|
||||||
|
|
||||||
private Pointers _pointers;
|
private Pointers _pointers;
|
||||||
|
|
||||||
private Pointers.Modifiers _mods;
|
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_kv = _shift_kv.withFlags(_shift_kv.getFlags() | KeyValue.FLAG_LOCK);
|
||||||
_shift_key = _keyboard.findKeyWithValue(_shift_kv);
|
_shift_key = _keyboard.findKeyWithValue(_shift_kv);
|
||||||
}
|
}
|
||||||
|
_compose_kv = KeyValue.getKeyByName("compose");
|
||||||
|
_compose_key = _keyboard.findKeyWithValue(_compose_kv);
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,26 +115,38 @@ public class Keyboard2View extends View
|
|||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Called by auto-capitalisation. */
|
void set_fake_ptr_latched(KeyboardData.Key key, KeyValue kv, boolean latched,
|
||||||
public void set_shift_state(boolean state, boolean lock)
|
boolean lock)
|
||||||
{
|
{
|
||||||
if (_keyboard == null || _shift_key == null)
|
if (_keyboard == null || key == null)
|
||||||
return;
|
return;
|
||||||
int flags = _pointers.getKeyFlags(_shift_key, _shift_kv);
|
int flags = _pointers.getKeyFlags(key, kv);
|
||||||
if (state)
|
if (latched)
|
||||||
{
|
{
|
||||||
if (flags != -1 && !lock)
|
if (flags != -1 && !lock)
|
||||||
return; // Don't replace an existing pointer
|
return; // Don't replace an existing pointer
|
||||||
_pointers.add_fake_pointer(_shift_kv, _shift_key, lock);
|
_pointers.add_fake_pointer(kv, key, lock);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if ((flags & KeyValue.FLAG_FAKE_PTR) == 0)
|
if ((flags & KeyValue.FLAG_FAKE_PTR) == 0)
|
||||||
return; // Don't remove locked pointers
|
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)
|
public KeyValue modifyKey(KeyValue k, Pointers.Modifiers mods)
|
||||||
{
|
{
|
||||||
if (_keyboard.modmap != null)
|
if (_keyboard.modmap != null)
|
||||||
|
Loading…
Reference in New Issue
Block a user