forked from extern/Unexpected-Keyboard
Fix slider movements changing input focus
The slider was repeatedly sending arrow keys, which change the focused input when the end of a text box is hit. A new key is added that implements cursor movements using the `InputConnection` API. The new keys are defined as `KeyValue.Editing`, which are no longer only context menu actions. The behavior when a selection has started is changed. The selection is modified instead of cleared even when shift isn't pressed or the selection would become empty. Fallbacks to sending arrow keys for editors that do not support the API, like Termux.
This commit is contained in:
parent
2dc0ce066d
commit
5123ce5417
@ -2,7 +2,7 @@
|
||||
<row height="0.95">
|
||||
<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="change_method" key3="switch_emoji" key4="config"/>
|
||||
<key width="4.4" key0="space" key7="switch_forward" key8="switch_backward" key5="left" key6="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"/>
|
||||
<key width="1.7" key0="enter" key1="loc voice_typing" key2="action"/>
|
||||
</row>
|
||||
|
@ -3,6 +3,8 @@ package juloo.keyboard2;
|
||||
import android.os.Looper;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.ExtractedText;
|
||||
import android.view.inputmethod.ExtractedTextRequest;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
|
||||
class KeyEventHandler implements Config.IKeyEventHandler
|
||||
@ -39,33 +41,9 @@ class KeyEventHandler implements Config.IKeyEventHandler
|
||||
case Char: send_text(String.valueOf(key.getChar())); break;
|
||||
case String: send_text(key.getString()); break;
|
||||
case Event: _recv.handle_event_key(key.getEvent()); break;
|
||||
case Keyevent:
|
||||
handleKeyUpWithModifier(key.getKeyevent(), mods);
|
||||
break;
|
||||
case Modifier:
|
||||
break;
|
||||
case Editing:
|
||||
send_context_menu_action(action_of_editing_key(key.getEditing()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int action_of_editing_key(KeyValue.Editing e)
|
||||
{
|
||||
switch (e)
|
||||
{
|
||||
case COPY: return android.R.id.copy;
|
||||
case PASTE: return android.R.id.paste;
|
||||
case CUT: return android.R.id.cut;
|
||||
case SELECT_ALL: return android.R.id.selectAll;
|
||||
case SHARE: return android.R.id.shareText;
|
||||
case PASTE_PLAIN: return android.R.id.pasteAsPlainText;
|
||||
case UNDO: return android.R.id.undo;
|
||||
case REDO: return android.R.id.redo;
|
||||
case REPLACE: return android.R.id.replaceText;
|
||||
case ASSIST: return android.R.id.textAssist;
|
||||
case AUTOFILL: return android.R.id.autofill;
|
||||
default: return -1; // sad
|
||||
case Keyevent: send_key_down_up(key.getKeyevent(), mods); break;
|
||||
case Modifier: break;
|
||||
case Editing: handle_editing_key(key.getEditing(), mods); break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,7 +86,7 @@ class KeyEventHandler implements Config.IKeyEventHandler
|
||||
/*
|
||||
* Don't set KeyEvent.FLAG_SOFT_KEYBOARD.
|
||||
*/
|
||||
void handleKeyUpWithModifier(int keyCode, Pointers.Modifiers mods)
|
||||
void send_key_down_up(int keyCode, Pointers.Modifiers mods)
|
||||
{
|
||||
int metaState = 0;
|
||||
for (int i = 0; i < mods.size(); i++)
|
||||
@ -147,6 +125,88 @@ class KeyEventHandler implements Config.IKeyEventHandler
|
||||
conn.performContextMenuAction(id);
|
||||
}
|
||||
|
||||
void handle_editing_key(KeyValue.Editing ev, Pointers.Modifiers mods)
|
||||
{
|
||||
switch (ev)
|
||||
{
|
||||
case COPY: send_context_menu_action(android.R.id.copy); break;
|
||||
case PASTE: send_context_menu_action(android.R.id.paste); break;
|
||||
case CUT: send_context_menu_action(android.R.id.cut); break;
|
||||
case SELECT_ALL: send_context_menu_action(android.R.id.selectAll); break;
|
||||
case SHARE: send_context_menu_action(android.R.id.shareText); break;
|
||||
case PASTE_PLAIN: send_context_menu_action(android.R.id.pasteAsPlainText); break;
|
||||
case UNDO: send_context_menu_action(android.R.id.undo); break;
|
||||
case REDO: send_context_menu_action(android.R.id.redo); break;
|
||||
case REPLACE: send_context_menu_action(android.R.id.replaceText); break;
|
||||
case ASSIST: send_context_menu_action(android.R.id.textAssist); break;
|
||||
case AUTOFILL: send_context_menu_action(android.R.id.autofill); break;
|
||||
case CURSOR_LEFT: move_cursor(-1, mods); break;
|
||||
case CURSOR_RIGHT: move_cursor(1, mods); break;
|
||||
}
|
||||
}
|
||||
|
||||
static ExtractedTextRequest _move_cursor_req = null;
|
||||
|
||||
/** Query the cursor position. The extracted text is empty. Returns [null] if
|
||||
the editor doesn't support this operation. */
|
||||
ExtractedText get_cursor_pos(InputConnection conn)
|
||||
{
|
||||
if (_move_cursor_req == null)
|
||||
{
|
||||
_move_cursor_req = new ExtractedTextRequest();
|
||||
_move_cursor_req.hintMaxChars = 0;
|
||||
}
|
||||
return conn.getExtractedText(_move_cursor_req, 0);
|
||||
}
|
||||
|
||||
/** Move the cursor right or left, if possible without sending key events.
|
||||
Unlike arrow keys, the selection is not removed even if shift is not on. */
|
||||
void move_cursor(int d, Pointers.Modifiers mods)
|
||||
{
|
||||
InputConnection conn = _recv.getCurrentInputConnection();
|
||||
if (conn == null)
|
||||
return;
|
||||
ExtractedText et = get_cursor_pos(conn);
|
||||
if (et == null) // Editor doesn't support moving the cursor
|
||||
{
|
||||
move_cursor_fallback(d, mods);
|
||||
return;
|
||||
}
|
||||
int sel_start = et.selectionStart;
|
||||
int sel_end = et.selectionEnd;
|
||||
// Continue expanding the selection even if shift is not pressed
|
||||
if (sel_end != sel_start)
|
||||
{
|
||||
sel_end += d;
|
||||
if (sel_end == sel_start) // Avoid making the selection empty
|
||||
sel_end += d;
|
||||
}
|
||||
else
|
||||
{
|
||||
sel_end += d;
|
||||
// Leave 'sel_start' where it is if shift is pressed
|
||||
if (!mods.has(KeyValue.Modifier.SHIFT))
|
||||
sel_start = sel_end;
|
||||
}
|
||||
conn.setSelection(sel_start, sel_end);
|
||||
}
|
||||
|
||||
/** Send arrow keys as a fallback for editors that do not support
|
||||
[getExtractedText] like Termux. */
|
||||
void move_cursor_fallback(int d, Pointers.Modifiers mods)
|
||||
{
|
||||
while (d < 0)
|
||||
{
|
||||
send_key_down_up(KeyEvent.KEYCODE_DPAD_LEFT, mods);
|
||||
d++;
|
||||
}
|
||||
while (d > 0)
|
||||
{
|
||||
send_key_down_up(KeyEvent.KEYCODE_DPAD_RIGHT, mods);
|
||||
d--;
|
||||
}
|
||||
}
|
||||
|
||||
public static interface IReceiver
|
||||
{
|
||||
public void handle_event_key(KeyValue.Event ev);
|
||||
|
@ -70,6 +70,8 @@ final class KeyValue
|
||||
SHARE,
|
||||
ASSIST,
|
||||
AUTOFILL,
|
||||
CURSOR_LEFT,
|
||||
CURSOR_RIGHT,
|
||||
}
|
||||
|
||||
public static enum Placeholder
|
||||
@ -321,6 +323,7 @@ final class KeyValue
|
||||
case "\\@": return makeStringKey("@");
|
||||
case "\\\\": return makeStringKey("\\");
|
||||
|
||||
/* Modifiers and dead-keys */
|
||||
case "shift": return modifierKey(0xE00A, Modifier.SHIFT, 0);
|
||||
case "ctrl": return modifierKey("Ctrl", Modifier.CTRL, 0);
|
||||
case "alt": return modifierKey("Alt", Modifier.ALT, 0);
|
||||
@ -351,6 +354,7 @@ final class KeyValue
|
||||
case "fn": return modifierKey("Fn", Modifier.FN, 0);
|
||||
case "meta": return modifierKey("Meta", Modifier.META, 0);
|
||||
|
||||
/* Special event keys */
|
||||
case "config": return eventKey(0xE004, Event.CONFIG, FLAG_SMALLER_FONT);
|
||||
case "switch_text": return eventKey("ABC", Event.SWITCH_TEXT, FLAG_SMALLER_FONT);
|
||||
case "switch_numeric": return eventKey("123+", Event.SWITCH_NUMERIC, FLAG_SMALLER_FONT);
|
||||
@ -365,6 +369,7 @@ final class KeyValue
|
||||
case "capslock": return eventKey(0xE012, Event.CAPS_LOCK, 0);
|
||||
case "voice_typing": return eventKey(0xE015, Event.SWITCH_VOICE_TYPING, FLAG_SMALLER_FONT);
|
||||
|
||||
/* Key events */
|
||||
case "esc": return keyeventKey("Esc", KeyEvent.KEYCODE_ESCAPE, FLAG_SMALLER_FONT);
|
||||
case "enter": return keyeventKey(0xE00E, KeyEvent.KEYCODE_ENTER, 0);
|
||||
case "up": return keyeventKey(0xE005, KeyEvent.KEYCODE_DPAD_UP, 0);
|
||||
@ -392,6 +397,7 @@ final class KeyValue
|
||||
case "f12": return keyeventKey("F12", KeyEvent.KEYCODE_F12, FLAG_SMALLER_FONT);
|
||||
case "tab": return keyeventKey(0xE00F, KeyEvent.KEYCODE_TAB, FLAG_SMALLER_FONT);
|
||||
|
||||
/* Spaces */
|
||||
case "\\t": return charKey("\\t", '\t', 0); // Send the tab character
|
||||
case "space": return charKey("\r", ' ', FLAG_KEY_FONT | FLAG_SECONDARY);
|
||||
case "nbsp": return charKey("\u237d", '\u00a0', FLAG_SMALLER_FONT);
|
||||
@ -408,10 +414,6 @@ final class KeyValue
|
||||
case "blt": return charKey("<", '>', 0);
|
||||
case "bgt": return charKey(">", '<', 0);
|
||||
|
||||
case "removed": return placeholderKey(Placeholder.REMOVED);
|
||||
case "f11_placeholder": return placeholderKey(Placeholder.F11);
|
||||
case "f12_placeholder": return placeholderKey(Placeholder.F12);
|
||||
|
||||
/* hebrew niqqud */
|
||||
case "qamats": return charKey("\u05E7\u05B8", '\u05B8', 0); // kamatz
|
||||
case "patah": return charKey("\u05E4\u05B7", '\u05B7', 0); // patach
|
||||
@ -443,6 +445,7 @@ final class KeyValue
|
||||
case "zwj": return charKey("zwj", '\u200D', 0); // zero-width joiner (provides ligature)
|
||||
case "zwnj": return charKey("zwnj", '\u200C', 0); // zero-width non joiner (prevents unintended ligature)
|
||||
|
||||
/* Editing keys */
|
||||
case "copy": return editingKey(0xE030, Editing.COPY);
|
||||
case "paste": return editingKey(0xE032, Editing.PASTE);
|
||||
case "cut": return editingKey(0xE031, Editing.CUT);
|
||||
@ -454,6 +457,15 @@ final class KeyValue
|
||||
case "replaceText": return editingKey("repl", Editing.REPLACE);
|
||||
case "textAssist": return editingKey(0xE038, Editing.ASSIST);
|
||||
case "autofill": return editingKey("auto", Editing.AUTOFILL);
|
||||
case "cursor_left": return editingKey(0xE008, Editing.CURSOR_LEFT);
|
||||
case "cursor_right": return editingKey(0xE006, Editing.CURSOR_RIGHT);
|
||||
|
||||
/* Placeholder keys */
|
||||
case "removed": return placeholderKey(Placeholder.REMOVED);
|
||||
case "f11_placeholder": return placeholderKey(Placeholder.F11);
|
||||
case "f12_placeholder": return placeholderKey(Placeholder.F12);
|
||||
|
||||
/* Fallback to a string key that types its name */
|
||||
default: return makeStringKey(name);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user