Compare commits

..

2 Commits

Author SHA1 Message Date
Jules Aguillon
e60304b30d prefs: Add 'delete_word' and 'forward_delete_word' to extra keys
Some checks failed
Make Apk CI / Build-Apk (push) Has been cancelled
Check translations / check-translations (push) Has been cancelled
Check layouts / check_layout.output (push) Has been cancelled
Check layouts / Generated files (push) Has been cancelled
The gesture combination is mentioned. Preferred position are added.
2025-03-01 16:50:54 +01:00
Jules Aguillon
a6f9c72eb3 Add 'delete_word' and 'forward_delete_word' keys
These keys are the equivalent of ctrl+backspace and ctrl+delete,
respectively.

They can be reached with Gesture+backspace and Gesture+delete
respectively.
2025-03-01 16:50:28 +01:00
23 changed files with 2376 additions and 2429 deletions

View File

@@ -25,30 +25,31 @@ Key values can be any of the following:
+ `⏯:keyevent:85` A play/pause key (which has no effect in most apps). + `⏯:keyevent:85` A play/pause key (which has no effect in most apps).
+ `my@:'my.email@domain.com'` A key that sends an arbitrary string + `my@:'my.email@domain.com'` A key that sends an arbitrary string
- A macro, `legend:key_def1,key_def2,...`. - A macro, `symbol:key_def1,key_def2,...`.
This results in a key with legend `legend` that behaves as if the sequence of `key_def` had been pressed in order. This results in a key that behaves as if the sequence of `key_def` had been pressed in order.
Examples: Examples:
+ `CA:ctrl,a,ctrl,c` A key with legend CA that sends the sequence `ctrl+a`, `ctrl+c`. + `CA:ctrl,a,ctrl,c` A key with legend CA that sends the sequence `ctrl+a`, `ctrl+c`.
+ `Cd:ctrl,backspace` A key with legend Cd that sends the shortcut `ctrl+backspace`. + `Cd:ctrl,backspace` A key with legend Cd that sends the shortcut `ctrl+backspace`.
### Escape codes ## Escape codes
Value | Escape code for
When defining a key value, several characters have special effects. If you want a character not to have its usual effect but to be taken literally, you should "escape" it in the usual way for XML:
To get this character... | ...you can type
:---- | :------ :---- | :------
A literal newline character, which is different from `enter` and `action` in certain apps. | `\n` `\?` | `?`
A literal tab character, which is different from `tab` in certain apps. | `\t` `\#` | `#`
`\` | `\\` `\@` | `@`
`&` | `&` `\n` | Literal newline character. This is different from `enter` and `action` in certain apps.
`<` | `&lt;` `\t` | Literal tab character. This is different from `tab` in certain apps.
`>` | `&gt;` `\\` | `\`
`"` | `&quot;`
The characters `?`, `#`, and `@` do not need to be escaped when writing custom layouts. Internally, they can be escaped by prepending backslash (by typing `\?`, `\#`, and `\@`). XML escape codes also work, including:
The characters `,` and `:` can be escaped in a key value, using single quotes. For example, this macro defines a key with legend `http` that sends a string containing `:`: `<key c="http:home,'https://'" />` For simplicity, `,` and `:` cannot be escaped in the key legend. Value | Escape code for
:------- | :------
`&amp;` | `&`
`&lt;` | `<`
`&gt;` | `>`
`&quot;` | `"`
## Modifiers ## Modifiers
System modifiers are sent to the app, which can take app-specific action. System modifiers are sent to the app, which can take app-specific action.

View File

@@ -1,6 +1,7 @@
package juloo.keyboard2; package juloo.keyboard2;
import android.os.Handler; import android.os.Handler;
import android.os.Looper;
import android.text.InputType; import android.text.InputType;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
@@ -26,9 +27,9 @@ public final class Autocapitalisation
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES |
InputType.TYPE_TEXT_FLAG_CAP_WORDS; InputType.TYPE_TEXT_FLAG_CAP_WORDS;
public Autocapitalisation(Handler h, Callback cb) public Autocapitalisation(Looper looper, Callback cb)
{ {
_handler = h; _handler = new Handler(looper);
_callback = cb; _callback = cb;
} }

View File

@@ -2,7 +2,6 @@ package juloo.keyboard2;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.os.Looper; import android.os.Looper;
import android.os.Handler;
import android.text.InputType; import android.text.InputType;
import android.view.KeyCharacterMap; import android.view.KeyCharacterMap;
import android.view.KeyEvent; import android.view.KeyEvent;
@@ -29,10 +28,10 @@ public final class KeyEventHandler
[setSelection] could be used instead. */ [setSelection] could be used instead. */
boolean _move_cursor_force_fallback = false; boolean _move_cursor_force_fallback = false;
public KeyEventHandler(IReceiver recv) public KeyEventHandler(Looper looper, IReceiver recv)
{ {
_recv = recv; _recv = recv;
_autocap = new Autocapitalisation(recv.getHandler(), _autocap = new Autocapitalisation(looper,
this.new Autocapitalisation_callback()); this.new Autocapitalisation_callback());
_mods = Pointers.Modifiers.EMPTY; _mods = Pointers.Modifiers.EMPTY;
} }
@@ -325,73 +324,32 @@ public final class KeyEventHandler
void evaluate_macro(KeyValue[] keys) void evaluate_macro(KeyValue[] keys)
{ {
if (keys.length == 0) final Pointers.Modifiers empty = Pointers.Modifiers.EMPTY;
return;
// Ignore modifiers that are activated at the time the macro is evaluated // Ignore modifiers that are activated at the time the macro is evaluated
mods_changed(Pointers.Modifiers.EMPTY); mods_changed(empty);
evaluate_macro_loop(keys, 0, Pointers.Modifiers.EMPTY, _autocap.pause()); Pointers.Modifiers mods = empty;
} final boolean autocap_paused = _autocap.pause();
for (KeyValue kv : keys)
/** Evaluate the macro asynchronously to make sure event are processed in the
right order. */
void evaluate_macro_loop(final KeyValue[] keys, int i, Pointers.Modifiers mods, final boolean autocap_paused)
{
boolean should_delay = false;
KeyValue kv = KeyModifier.modify(keys[i], mods);
if (kv != null)
{ {
kv = KeyModifier.modify(kv, mods);
if (kv == null)
continue;
if (kv.hasFlagsAny(KeyValue.FLAG_LATCH)) if (kv.hasFlagsAny(KeyValue.FLAG_LATCH))
{ {
// Non-special latchable keys clear latched modifiers // Non-special latchable keys clear latched modifiers
if (!kv.hasFlagsAny(KeyValue.FLAG_SPECIAL)) if (!kv.hasFlagsAny(KeyValue.FLAG_SPECIAL))
mods = Pointers.Modifiers.EMPTY; mods = empty;
mods = mods.with_extra_mod(kv); mods = mods.with_extra_mod(kv);
} }
else else
{ {
key_down(kv, false); key_down(kv, false);
key_up(kv, mods); key_up(kv, mods);
mods = Pointers.Modifiers.EMPTY; mods = empty;
} }
should_delay = wait_after_macro_key(kv);
} }
i++;
if (i >= keys.length) // Stop looping
{
_autocap.unpause(autocap_paused); _autocap.unpause(autocap_paused);
} }
else if (should_delay)
{
// Add a delay before sending the next key to avoid race conditions
// causing keys to be handled in the wrong order. Notably, KeyEvent keys
// handling is scheduled differently than the other edit functions.
final int i_ = i;
final Pointers.Modifiers mods_ = mods;
_recv.getHandler().postDelayed(new Runnable() {
public void run()
{
evaluate_macro_loop(keys, i_, mods_, autocap_paused);
}
}, 1000/30);
}
else
evaluate_macro_loop(keys, i, mods, autocap_paused);
}
boolean wait_after_macro_key(KeyValue kv)
{
switch (kv.getKind())
{
case Keyevent:
case Editing:
case Event:
return true;
case Slider:
return _move_cursor_force_fallback;
default:
return false;
}
}
/** Repeat calls to [send_key_down_up]. */ /** Repeat calls to [send_key_down_up]. */
void send_key_down_up_repeat(int event_code, int repeat) void send_key_down_up_repeat(int event_code, int repeat)
@@ -406,7 +364,6 @@ public final class KeyEventHandler
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 void set_compose_pending(boolean pending);
public InputConnection getCurrentInputConnection(); public InputConnection getCurrentInputConnection();
public Handler getHandler();
} }
class Autocapitalisation_callback implements Autocapitalisation.Callback class Autocapitalisation_callback implements Autocapitalisation.Callback

View File

@@ -6,7 +6,6 @@ import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.inputmethodservice.InputMethodService; import android.inputmethodservice.InputMethodService;
import android.os.Build.VERSION; import android.os.Build.VERSION;
import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.text.InputType; import android.text.InputType;
import android.util.Log; import android.util.Log;
@@ -39,7 +38,6 @@ public class Keyboard2 extends InputMethodService
private ViewGroup _emojiPane = null; private ViewGroup _emojiPane = null;
private ViewGroup _clipboard_pane = null; private ViewGroup _clipboard_pane = null;
public int actionId; // Action performed by the Action key. public int actionId; // Action performed by the Action key.
private Handler _handler;
private Config _config; private Config _config;
@@ -109,8 +107,7 @@ public class Keyboard2 extends InputMethodService
{ {
super.onCreate(); super.onCreate();
SharedPreferences prefs = DirectBootAwarePreferences.get_shared_preferences(this); SharedPreferences prefs = DirectBootAwarePreferences.get_shared_preferences(this);
_handler = new Handler(getMainLooper()); _keyeventhandler = new KeyEventHandler(getMainLooper(), this.new Receiver());
_keyeventhandler = new KeyEventHandler(this.new Receiver());
Config.initGlobalConfig(prefs, getResources(), _keyeventhandler); Config.initGlobalConfig(prefs, getResources(), _keyeventhandler);
prefs.registerOnSharedPreferenceChangeListener(this); prefs.registerOnSharedPreferenceChangeListener(this);
_config = Config.globalConfig(); _config = Config.globalConfig();
@@ -484,11 +481,6 @@ public class Keyboard2 extends InputMethodService
{ {
return Keyboard2.this.getCurrentInputConnection(); return Keyboard2.this.getCurrentInputConnection();
} }
public Handler getHandler()
{
return _handler;
}
} }
private IBinder getConnectionToken() private IBinder getConnectionToken()

View File

@@ -74,11 +74,7 @@ def sync_metadata(value_dir, strings):
os.makedirs(meta_dir) os.makedirs(meta_dir)
txt_file = os.path.join(meta_dir, fname) txt_file = os.path.join(meta_dir, fname)
with open(txt_file, "w", encoding="utf-8") as out: with open(txt_file, "w", encoding="utf-8") as out:
out.write(string.text out.write(string.text.removeprefix('"').removesuffix('"'))
.replace("\\n", "\n")
.replace("\\'", "'")
.removeprefix('"')
.removesuffix('"'))
out.write("\n") out.write("\n")
sync_meta_file("title.txt", ("app_name_release", None)) sync_meta_file("title.txt", ("app_name_release", None))
sync_meta_file("short_description.txt", ("short_description", None)) sync_meta_file("short_description.txt", ("short_description", None))