From de328978f1124f2987d438ef94a1ba66a34d21f5 Mon Sep 17 00:00:00 2001 From: ZHAO Xudong Date: Sun, 9 Aug 2026 10:21:57 +0800 Subject: [PATCH 1/4] [r2] --- .github/workflows/dev-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index dba0536..0e618fc 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -1,6 +1,6 @@ name: Dev Build Android APK -# Lightweight dev build: builds debug + release APKs, uploads them as +# Lightweight dev build: builds debug + release APKs, uploads them as # GitHub Actions artifacts, and optionally mirrors release APKs to Cloudflare # R2 when the commit message contains [r2]. # From 36812da74277b02e8ce9ac512c83b16930c3c9db Mon Sep 17 00:00:00 2001 From: ZHAO Xudong Date: Sun, 9 Aug 2026 15:30:49 +0800 Subject: [PATCH 2/4] [r2] --- .../org/electerm/electerm/MainActivity.java | 79 +++++++++++++++---- 1 file changed, 62 insertions(+), 17 deletions(-) diff --git a/build/android/res-overlay/java/org/electerm/electerm/MainActivity.java b/build/android/res-overlay/java/org/electerm/electerm/MainActivity.java index d437951..84d055d 100644 --- a/build/android/res-overlay/java/org/electerm/electerm/MainActivity.java +++ b/build/android/res-overlay/java/org/electerm/electerm/MainActivity.java @@ -1,37 +1,82 @@ package org.electerm.electerm; import android.os.Bundle; +import android.view.View; import androidx.core.view.WindowCompat; import com.getcapacitor.BridgeActivity; /** * electerm custom MainActivity. * - * Problem: On Android 15+ (API 35+, e.g. Google Pixel 9), edge-to-edge display - * is enforced. Capacitor's BridgeActivity.onCreate() calls + * Problem: On Android 15+ (API 35+, e.g. Google Pixel 9), Capacitor's + * BridgeActivity enables edge-to-edge display by calling * WindowCompat.setDecorFitsSystemWindows(getWindow(), false) - * which opts INTO edge-to-edge, causing the WebView content to extend under the - * system status bar. This makes the tab bar area (top 36px of the app) - * unclickable — the status bar overlays it. + * and setting up its own OnApplyWindowInsetsListener that consumes system + * bar insets without applying them as padding. This causes the WebView + * content to extend under the status bar, making the tab bar area + * unclickable. * - * On some devices (e.g. Huawei Mate 40 running EMUI / older Android) this does - * not happen because the manufacturer's system does not enforce edge-to-edge or - * handles insets differently. + * Simply calling setDecorFitsSystemWindows(true) is NOT enough because + * Capacitor's listener still consumes the insets without applying padding. * - * Fix: Override BridgeActivity.onCreate() and call - * WindowCompat.setDecorFitsSystemWindows(getWindow(), true) - * AFTER super.onCreate(). This tells the system to apply automatic padding for - * status-bar and navigation-bar insets, so the WebView content stays within the - * safe area and the tab bar is fully clickable on all devices. - * - * This is the app-level (native) fix — no CSS safe-area-inset hacks needed. + * Fix: Override the content view's OnApplyWindowInsetsListener to apply + * system bar insets (status bar + navigation bar) as padding on the root + * content FrameLayout. This constrains the WebView within the safe area. + * The listener is re-applied in onPostCreate/onResume and via post() to + * ensure it runs after Capacitor's bridge initialization. */ public class MainActivity extends BridgeActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // Override Capacitor's edge-to-edge default so content does not extend - // under system bars (status bar at top, navigation bar at bottom). WindowCompat.setDecorFitsSystemWindows(getWindow(), true); + applySafeAreaPadding(); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + applySafeAreaPadding(); + } + + @Override + protected void onResume() { + super.onResume(); + WindowCompat.setDecorFitsSystemWindows(getWindow(), true); + applySafeAreaPadding(); + } + + /** + * Apply system bar insets (status bar + navigation bar) as padding to + * the root content view (android.R.id.content) so the WebView stays + * within the safe area. + * + * We override any OnApplyWindowInsetsListener that Capacitor's Bridge + * may have set on the content view, and apply the insets as padding. + * The listener is set both synchronously and via post() to ensure it + * runs after Capacitor's (possibly async) bridge initialization. + */ + private void applySafeAreaPadding() { + View contentView = findViewById(android.R.id.content); + if (contentView == null) return; + + setInsetListener(contentView); + // Also post to run after all pending main-thread messages, + // which is after Capacitor's async bridge setup. + contentView.post(() -> setInsetListener(contentView)); + } + + private void setInsetListener(View contentView) { + contentView.setOnApplyWindowInsetsListener((v, insets) -> { + int top = insets.getSystemWindowInsetTop(); + int bottom = insets.getSystemWindowInsetBottom(); + v.setPadding(0, top, 0, bottom); + // Consume system window insets so the WebView (child) doesn't + // double-apply them. The keyboard is handled via + // android:windowSoftInputMode=adjustResize in the theme, which + // resizes the window rather than relying on insets dispatch. + return insets.consumeSystemWindowInsets(); + }); + contentView.requestApplyInsets(); } } From c215d0d30d233bfee4561dd74917ac59b5248250 Mon Sep 17 00:00:00 2001 From: ZHAO Xudong Date: Sun, 9 Aug 2026 15:55:44 +0800 Subject: [PATCH 3/4] FIx [r2] --- .../res-overlay/java/org/electerm/electerm/MainActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/android/res-overlay/java/org/electerm/electerm/MainActivity.java b/build/android/res-overlay/java/org/electerm/electerm/MainActivity.java index 84d055d..cc28b01 100644 --- a/build/android/res-overlay/java/org/electerm/electerm/MainActivity.java +++ b/build/android/res-overlay/java/org/electerm/electerm/MainActivity.java @@ -40,7 +40,7 @@ protected void onPostCreate(Bundle savedInstanceState) { } @Override - protected void onResume() { + public void onResume() { super.onResume(); WindowCompat.setDecorFitsSystemWindows(getWindow(), true); applySafeAreaPadding(); From 6bfbb0b6f061ffc1860986a0ddf3ecf4283d584b Mon Sep 17 00:00:00 2001 From: ZHAO Xudong Date: Sun, 9 Aug 2026 19:15:57 +0800 Subject: [PATCH 4/4] Add keyboard shortcut when input --- .../web-components/keyboard-accessory-bar.jsx | 419 ++++++++++++++++++ .../keyboard-accessory-bar.styl | 82 ++++ src/client/web-components/web-main.jsx | 2 + 3 files changed, 503 insertions(+) create mode 100644 src/client/web-components/keyboard-accessory-bar.jsx create mode 100644 src/client/web-components/keyboard-accessory-bar.styl diff --git a/src/client/web-components/keyboard-accessory-bar.jsx b/src/client/web-components/keyboard-accessory-bar.jsx new file mode 100644 index 0000000..502b02e --- /dev/null +++ b/src/client/web-components/keyboard-accessory-bar.jsx @@ -0,0 +1,419 @@ +/** + * Mobile keyboard accessory bar. + * + * When any input (including the xterm terminal helper textarea) is focused on + * a touch device, the Android system soft keyboard appears. This component + * renders a fixed toolbar above the keyboard with extra control keys that are + * hard or impossible to type on a mobile keyboard: + * + * ESC, Tab, Ctrl (toggle), Alt (toggle), arrow keys, Ctrl-C, Ctrl-D, + * Ctrl-Z, Ctrl-L, Ctrl-R, Ctrl-A, Ctrl-E, Ctrl-W, Ctrl-U, Ctrl-K, + * Home, End, PgUp, PgDn, /, :, -, `, \, Backspace, and a "Done" button + * to dismiss the keyboard. + * + * For the terminal: + * - Keys are sent directly to the active terminal's AttachAddon._sendData, + * bypassing xterm's textarea entirely (reliable, no focus-juggling). + * - The Ctrl toggle wraps _sendData so the NEXT character typed on the soft + * keyboard is converted to Ctrl+char, then auto-disables. This lets you + * type Ctrl+any letter, not just the pre-defined shortcuts. + * - The Alt toggle sends an ESC prefix (\x1b) before the next character. + * + * For regular /