From af28ee570c7ee0b2f69256d61c93668223dcf877 Mon Sep 17 00:00:00 2001 From: mister-teddy Date: Sat, 31 Oct 2026 14:54:07 +0100 Subject: [PATCH 1/7] feat: drive the external display with a mouse plugged into the phone, logo in the top left --- src/android/backend/wayland/event_handler.rs | 16 +- src/android/dex/classes.dex | Bin 23972 -> 26952 bytes src/android/external_display.rs | 38 ++- .../java/app/polarbear/TrackpadControls.java | 237 ++++++++++++++---- 4 files changed, 240 insertions(+), 51 deletions(-) diff --git a/src/android/backend/wayland/event_handler.rs b/src/android/backend/wayland/event_handler.rs index e78769c..2cb07d9 100644 --- a/src/android/backend/wayland/event_handler.rs +++ b/src/android/backend/wayland/event_handler.rs @@ -468,14 +468,22 @@ pub fn paint_trackpad(backend: &mut WaylandBackend) { } } -/// A mouse on the external display: it points where it is, and its buttons and wheel go -/// straight to the desktop. +/// A mouse on the external display, or one plugged into the phone: it moves the cursor, and its +/// buttons and wheel go straight to the desktop. pub fn on_external_pointer(backend: &mut WaylandBackend, event: PointerEvent) { let time = event.time_ms as u32; let size = backend.compositor.state.size; + let (x, y) = if event.relative { + // Mouse counts, scaled up on monitors larger than 1080p so crossing them is no chore. + let speed = (size.w as f64 / 1920.0).max(1.0); + let cursor = backend.cursor_position; + (cursor.x + event.x * speed, cursor.y + event.y * speed) + } else { + (event.x, event.y) + }; backend.cursor_position = ( - event.x.clamp(0.0, (size.w - 1).max(0) as f64), - event.y.clamp(0.0, (size.h - 1).max(0) as f64), + x.clamp(0.0, (size.w - 1).max(0) as f64), + y.clamp(0.0, (size.h - 1).max(0) as f64), ) .into(); let cursor = backend.cursor_position; diff --git a/src/android/dex/classes.dex b/src/android/dex/classes.dex index 69d7252489b39f9dc38860b903ebe16b8f69dbfc..efd3d07c5d74fa3729f37a03e1aa64c6aa16a8ea 100644 GIT binary patch literal 26952 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 SXI1`Y9PeT4@(=eb^?w0!s8HDe literal 23972 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 diff --git a/src/android/external_display.rs b/src/android/external_display.rs index 728641a..e4d9090 100644 --- a/src/android/external_display.rs +++ b/src/android/external_display.rs @@ -61,11 +61,13 @@ impl Drop for ExternalSurface { } } -/// A mouse (or touch) on the external monitor, in its pixels. +/// A mouse (or touch) on the external monitor, or a mouse plugged into the phone. #[derive(Debug, Clone, Copy)] pub struct PointerEvent { + /// Where on the monitor, in its pixels, or how far a phone's mouse moved when `relative`. pub x: f64, pub y: f64, + pub relative: bool, /// `MotionEvent.getButtonState()`. pub buttons: i32, /// `AXIS_HSCROLL` and `AXIS_VSCROLL`, in wheel notches. @@ -231,16 +233,44 @@ pub extern "system" fn Java_app_polarbear_ExternalDisplay_nativeOnPointer( scroll_y: jfloat, event_time_ms: jlong, ) { - let mut bridge = bridge().0.lock().unwrap(); - let wake = bridge.pointer_events.is_empty(); - bridge.pointer_events.push_back(PointerEvent { + push_pointer_event(PointerEvent { x: x as f64, y: y as f64, + relative: false, buttons, scroll_x: scroll_x as f64, scroll_y: scroll_y as f64, time_ms: event_time_ms.max(0) as u64, }); +} + +/// A mouse plugged into the phone, captured while it is a trackpad. +#[no_mangle] +pub extern "system" fn Java_app_polarbear_TrackpadControls_nativeOnMouse( + _env: JNIEnv, + _class: JClass, + dx: jfloat, + dy: jfloat, + buttons: jint, + scroll_x: jfloat, + scroll_y: jfloat, + event_time_ms: jlong, +) { + push_pointer_event(PointerEvent { + x: dx as f64, + y: dy as f64, + relative: true, + buttons, + scroll_x: scroll_x as f64, + scroll_y: scroll_y as f64, + time_ms: event_time_ms.max(0) as u64, + }); +} + +fn push_pointer_event(event: PointerEvent) { + let mut bridge = bridge().0.lock().unwrap(); + let wake = bridge.pointer_events.is_empty(); + bridge.pointer_events.push_back(event); if wake { notify(&bridge, AppUserEvent::ExternalPointer); } diff --git a/src/android/java/app/polarbear/TrackpadControls.java b/src/android/java/app/polarbear/TrackpadControls.java index de11b5b..5815c2c 100644 --- a/src/android/java/app/polarbear/TrackpadControls.java +++ b/src/android/java/app/polarbear/TrackpadControls.java @@ -12,11 +12,14 @@ import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.hardware.input.InputManager; import android.os.Build; import android.os.Bundle; import android.text.InputType; import android.view.Gravity; +import android.view.InputDevice; import android.view.KeyEvent; +import android.view.MotionEvent; import android.view.Surface; import android.view.View; import android.view.Window; @@ -28,11 +31,18 @@ import android.view.inputmethod.InputMethodManager; import android.widget.LinearLayout; -/** Buttons on the phone while it is a trackpad: turn the screen, and type with the Android keyboard. */ -final class TrackpadControls extends Dialog { +/** + * Buttons on the phone while it is a trackpad: turn the screen, and type with the Android + * keyboard. A mouse plugged into the phone moves the cursor on the external display. + */ +final class TrackpadControls extends Dialog implements InputManager.InputDeviceListener { private final Activity activity; private final Pad pad; + private final Badge badge; + private LinearLayout bar; private KeyboardButton keyboard; + /** A mouse is plugged in, so this window keeps focus to capture it. */ + private boolean mouseAttached; private boolean keyboardShown; /** Waiting for the window to take focus before the keyboard can come up. */ private boolean keyboardPending; @@ -43,12 +53,14 @@ final class TrackpadControls extends Dialog { super(activity); this.activity = activity; pad = new Pad(activity); + badge = new Badge(activity); } @Override public void show() { // Under the buttons. pad.show(); + badge.show(); super.show(); } @@ -64,13 +76,9 @@ protected void onCreate(Bundle savedInstanceState) { // Touches elsewhere reach the trackpad, and keys the activity, until the keyboard is up. window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); - window.setGravity(Gravity.TOP | Gravity.RIGHT); - WindowManager.LayoutParams attributes = window.getAttributes(); - attributes.x = dp(Pad.INSET + 8); - attributes.y = dp(Pad.INSET + 8); - window.setAttributes(attributes); + placeInCorner(window, Gravity.RIGHT); - LinearLayout bar = new LinearLayout(getContext()); + bar = new LinearLayout(getContext()); bar.setOrientation(LinearLayout.HORIZONTAL); IconButton rotate = new IconButton(getContext(), IconButton.ROTATE); style(rotate, "Rotate screen"); @@ -92,6 +100,21 @@ public void onClick(View view) { bar.addView(keyboard); setContentView(bar); + if (Build.VERSION.SDK_INT >= 26) { + View.OnCapturedPointerListener mouse = new View.OnCapturedPointerListener() { + @Override + public boolean onCapturedPointer(View view, MotionEvent event) { + return onMouse(event); + } + }; + // Captured events go to whichever of these has focus. + bar.setOnCapturedPointerListener(mouse); + keyboard.setOnCapturedPointerListener(mouse); + InputManager inputManager = (InputManager) getContext().getSystemService(Context.INPUT_SERVICE); + inputManager.registerInputDeviceListener(this, null); + updateMouse(); + } + if (Build.VERSION.SDK_INT >= 30) { // The keyboard can also go away with the back gesture. bar.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { @@ -112,6 +135,11 @@ public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) { @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); + // Capture ends whenever the window loses focus, e.g. to the notification shade. + if (hasFocus && mouseAttached && Build.VERSION.SDK_INT >= 26) { + View focused = getWindow().getDecorView().findFocus(); + (focused != null ? focused : bar).requestPointerCapture(); + } if (hasFocus && keyboardPending) { keyboardPending = false; // After the input method has connected to the newly focused button. @@ -131,11 +159,89 @@ public void run() { @Override public void dismiss() { + if (Build.VERSION.SDK_INT >= 26) { + ((InputManager) getContext().getSystemService(Context.INPUT_SERVICE)).unregisterInputDeviceListener(this); + } setKeyboardShown(false); pad.dismiss(); + badge.dismiss(); super.dismiss(); } + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + // Keys from the Android keyboard, and from a physical one while this window has focus. + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { + return super.dispatchKeyEvent(event); + } + forwardKey(event); + return true; + } + + @Override + public void onInputDeviceAdded(int deviceId) { + updateMouse(); + } + + @Override + public void onInputDeviceRemoved(int deviceId) { + updateMouse(); + } + + @Override + public void onInputDeviceChanged(int deviceId) { + updateMouse(); + } + + /** + * With a mouse plugged in, take focus and capture it: Android hides its pointer on the phone, + * and the mouse moves the cursor on the external display instead. + */ + private void updateMouse() { + boolean attached = false; + for (int id : InputDevice.getDeviceIds()) { + InputDevice device = InputDevice.getDevice(id); + // A captured mouse reports itself as relative only, so both count. + if (device != null && !device.isVirtual() + && (device.supportsSource(InputDevice.SOURCE_MOUSE) + || device.supportsSource(InputDevice.SOURCE_MOUSE_RELATIVE))) { + attached = true; + } + } + if (attached == mouseAttached) { + return; + } + mouseAttached = attached; + if (attached) { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); + bar.setFocusableInTouchMode(true); + bar.requestFocus(); + } else if (!keyboardShown) { + bar.setFocusableInTouchMode(false); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); + } + } + + private boolean onMouse(MotionEvent event) { + // A captured mouse reports how far it moved, batched since the last event. + float dx = event.getX(); + float dy = event.getY(); + for (int i = 0; i < event.getHistorySize(); i++) { + dx += event.getHistoricalX(i); + dy += event.getHistoricalY(i); + } + float scrollX = 0; + float scrollY = 0; + if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) { + dx = 0; + dy = 0; + scrollX = event.getAxisValue(MotionEvent.AXIS_HSCROLL); + scrollY = event.getAxisValue(MotionEvent.AXIS_VSCROLL); + } + nativeOnMouse(dx, dy, event.getButtonState(), scrollX, scrollY, event.getEventTime()); + return true; + } + /** * Turns the whole screen a quarter clockwise, Android keyboard included, for a phone lying * sideways while auto-rotate keeps it upright. @@ -180,12 +286,26 @@ private void setKeyboardShown(boolean shown) { keyboardPending = false; imm.hideSoftInputFromWindow(keyboard.getWindowToken(), 0); keyboard.setFocusableInTouchMode(false); - window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); + if (mouseAttached) { + bar.requestFocus(); + } else { + window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); + } } keyboard.setSelected(shown); keyboard.invalidate(); } + /** The buttons' corner of the trackpad, mirrored by the badge's. */ + private static void placeInCorner(Window window, int side) { + window.setGravity(Gravity.TOP | side); + WindowManager.LayoutParams attributes = window.getAttributes(); + float density = window.getContext().getResources().getDisplayMetrics().density; + attributes.x = Math.round((Pad.INSET + 8) * density); + attributes.y = Math.round((Pad.INSET + 8) * density); + window.setAttributes(attributes); + } + private void style(View view, String label) { view.setContentDescription(label); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(dp(44), dp(44)); @@ -198,8 +318,8 @@ private int dp(int value) { } /** - * The trackpad itself, drawn like Samsung DeX: a border, a cross through the middle, and the - * app's logo on top so the right way up is clear. Touches go through it to the activity. + * The trackpad itself, drawn like Samsung DeX: a border and a cross through the middle. + * Touches go through it to the activity. */ private static final class Pad extends Dialog { static final int INSET = 12; @@ -233,25 +353,10 @@ protected void onCreate(Bundle savedInstanceState) { private static final class PadView extends View { private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - private final Drawable logo; - /** `logo` is an adaptive icon layer, with the art in its middle two thirds. */ - private final boolean logoIsLayer; PadView(Context context) { super(context); paint.setStyle(Paint.Style.STROKE); - Drawable icon = context.getApplicationInfo().loadIcon(context.getPackageManager()); - if (Build.VERSION.SDK_INT >= 33 && icon instanceof AdaptiveIconDrawable - && ((AdaptiveIconDrawable) icon).getMonochrome() != null) { - // The silhouette, in the pad's grey. - logo = ((AdaptiveIconDrawable) icon).getMonochrome().mutate(); - logo.setColorFilter(0xFF8A8A8A, PorterDuff.Mode.SRC_IN); - logoIsLayer = true; - } else { - // An icon without one (a full-bleed picture) as the launcher shows it. - logo = icon; - logoIsLayer = false; - } } @Override @@ -269,15 +374,63 @@ protected void onDraw(Canvas canvas) { paint.setStrokeWidth(1.5f * density); paint.setColor(0xFF4A4A4A); canvas.drawRoundRect(border, 16 * density, 16 * density, paint); + } + } - // Over the top of the cross, on a patch of the pad's black. - int size = Math.round(48 * density); - int left = Math.round(centerX - size / 2f); - int top = Math.round(border.top + 12 * density); - paint.setStyle(Paint.Style.FILL); - paint.setColor(Color.BLACK); - canvas.drawRect(left, top, left + size, top + size, paint); - paint.setStyle(Paint.Style.STROKE); + /** + * The app's logo in the top left corner, across from the buttons, so the right way up is + * clear. Touches go through it to the activity. + */ + private final class Badge extends Dialog { + Badge(Activity activity) { + super(activity); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + requestWindowFeature(Window.FEATURE_NO_TITLE); + super.onCreate(savedInstanceState); + setCancelable(false); + Window window = getWindow(); + window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); + window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); + placeInCorner(window, Gravity.LEFT); + LinearLayout frame = new LinearLayout(getContext()); + View logo = new LogoView(getContext()); + style(logo, null); + frame.addView(logo); + setContentView(frame); + } + } + + private static final class LogoView extends View { + private final Drawable logo; + /** `logo` is an adaptive icon layer, with the art in its middle two thirds. */ + private final boolean logoIsLayer; + + LogoView(Context context) { + super(context); + Drawable icon = context.getApplicationInfo().loadIcon(context.getPackageManager()); + if (Build.VERSION.SDK_INT >= 33 && icon instanceof AdaptiveIconDrawable + && ((AdaptiveIconDrawable) icon).getMonochrome() != null) { + // The silhouette, in the pad's grey. + logo = ((AdaptiveIconDrawable) icon).getMonochrome().mutate(); + logo.setColorFilter(0xFF8A8A8A, PorterDuff.Mode.SRC_IN); + logoIsLayer = true; + } else { + // An icon without one (a full-bleed picture) as the launcher shows it. + logo = icon; + logoIsLayer = false; + } + } + + @Override + protected void onDraw(Canvas canvas) { + int size = Math.min(getWidth(), getHeight()); + int left = (getWidth() - size) / 2; + int top = (getHeight() - size) / 2; int bleed = logoIsLayer ? size / 4 : 0; logo.setBounds(left - bleed, top - bleed, left + size + bleed, top + size + bleed); logo.draw(canvas); @@ -360,23 +513,21 @@ public InputConnection onCreateInputConnection(EditorInfo outAttrs) { | EditorInfo.IME_ACTION_NONE; return new BaseInputConnection(this, false); } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { - return super.dispatchKeyEvent(event); - } - forwardKey(event); - return true; - } } /** Passes a key on to the desktop, from the Android keyboard or one typing on the monitor. */ static void forwardKey(KeyEvent event) { + // Apps on the desktop repeat held keys themselves. + if (event.getRepeatCount() > 0) { + return; + } if (event.getAction() == KeyEvent.ACTION_DOWN || event.getAction() == KeyEvent.ACTION_UP) { nativeOnKey(event.getAction() == KeyEvent.ACTION_DOWN, event.getKeyCode(), event.getEventTime()); } } private static native void nativeOnKey(boolean down, int keyCode, long eventTimeMs); + + private static native void nativeOnMouse( + float dx, float dy, int buttons, float scrollX, float scrollY, long eventTimeMs); } From dbf5efb9642ceedd1d6647dbff710b979daf36c8 Mon Sep 17 00:00:00 2001 From: mister-teddy Date: Sat, 31 Oct 2026 15:24:02 +0100 Subject: [PATCH 2/7] fix: give sudo, su and passwd their setuid bit so sudo works for the desktop user --- src/android/proot/setup.rs | 40 +++++++++++++++++++++++++++++ src/core/rootfs.rs | 52 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/src/android/proot/setup.rs b/src/android/proot/setup.rs index 151d966..a38257c 100644 --- a/src/android/proot/setup.rs +++ b/src/android/proot/setup.rs @@ -142,6 +142,45 @@ fn setup_rootfs(options: &SetupOptions) -> StageOutput { })) } +/// Root filesystems unpacked before setuid programs kept their proot metadata: read the archive +/// once more to give sudo, su and passwd back their setuid bit, without unpacking it again. +fn setup_setuid_metadata(options: &SetupOptions) -> StageOutput { + let rootfs_dir = get_application_context().rootfs_dir; + if !rootfs::needs_setuid_metadata(&rootfs_dir) { + return None; + } + + let android_app = options.android_app.clone(); + let mpsc_sender = options.mpsc_sender.clone(); + let total = options.manifest.uncompressed_size.max(1); + Some(thread::spawn(move || { + let name = CString::new(ROOTFS_ARCHIVE).expect("asset name has no NUL bytes"); + let Some(asset) = android_app.asset_manager().open(&name) else { + log::error!("This APK does not contain {ROOTFS_ARCHIVE}"); + return; + }; + let mut last_percent = None; + let result = rootfs::restore_setuid_metadata( + BufReader::with_capacity(1 << 20, asset), + &rootfs_dir, + |read| { + let percent = (read * 100 / total).min(100); + if last_percent != Some(percent) { + last_percent = Some(percent); + mpsc_sender + .send(SetupMessage::Progress(format!( + "Setting up sudo... {percent}%" + ))) + .unwrap_or(()); + } + }, + ); + if let Err(error) = result { + log::error!("Failed to restore setuid programs: {error}"); + } + })) +} + fn simulate_linux_sysdata_stage(options: &SetupOptions) -> StageOutput { let fs_root = get_application_context().rootfs_dir; let mpsc_sender = options.mpsc_sender.clone(); @@ -497,6 +536,7 @@ pub fn setup(android_app: AndroidApp) -> PolarBearBackend { let stages: Vec = vec![ Box::new(setup_rootfs), // Step 1. Unpack the OS bundled in the APK + Box::new(setup_setuid_metadata), // Step 1b. sudo for an OS unpacked by an older version Box::new(simulate_linux_sysdata_stage), // Step 2. Simulate Linux system data Box::new(setup_home), // Step 3. Persistent /home and /etc/localdesktop Box::new(setup_machine_id), // Step 4. Seed /etc/machine-id for D-Bus clients diff --git a/src/core/rootfs.rs b/src/core/rootfs.rs index 4d30777..98a7c67 100644 --- a/src/core/rootfs.rs +++ b/src/core/rootfs.rs @@ -30,6 +30,10 @@ const STAMP_FILE: &str = ".elementary-mobile-rootfs"; /// Suffix of the directory an archive is unpacked into before it is moved into place. const PARTIAL_SUFFIX: &str = "partial"; +/// File inside a root filesystem whose setuid programs (sudo, su, passwd) have their proot +/// metadata. Root filesystems unpacked before it existed get it from `restore_setuid_metadata`. +const SETUID_STAMP: &str = ".elementary-mobile-setuid"; + #[derive(Debug, Clone, Deserialize)] pub struct RootfsManifest { /// SHA-256 of the archive. Changes whenever the bundled OS does. @@ -113,6 +117,7 @@ pub fn install( } _ => { entry.unpack_in(&partial)?; + write_setuid_metadata(&partial, &entry)?; // The guest runs as a fake root that should be able to change any file, but the // kernel still checks the owner bits of the app's own user. if kind == EntryType::Regular && mode & 0o600 != 0o600 { @@ -135,9 +140,56 @@ pub fn install( progress(read_bytes.get()); fs::write(partial.join(STAMP_FILE), &manifest.id)?; + fs::write(partial.join(SETUID_STAMP), "")?; fs::rename(&partial, rootfs) } +/// Whether `rootfs` was unpacked before setuid programs got their proot metadata. +pub fn needs_setuid_metadata(rootfs: &Path) -> bool { + rootfs.join(STAMP_FILE).exists() && !rootfs.join(SETUID_STAMP).exists() +} + +/// Give the setuid programs of an already unpacked `rootfs` their proot metadata, reading +/// `archive` (the one it was unpacked from) without unpacking it again. +pub fn restore_setuid_metadata( + archive: R, + rootfs: &Path, + mut progress: impl FnMut(u64), +) -> io::Result<()> { + let read_bytes = Rc::new(Cell::new(0u64)); + let reader = CountingReader { + inner: XzDecoder::new(archive), + count: read_bytes.clone(), + }; + let mut archive = Archive::new(reader); + for entry in archive.entries()? { + write_setuid_metadata(rootfs, &entry?)?; + progress(read_bytes.get()); + } + fs::write(rootfs.join(SETUID_STAMP), "") +} + +/// Android does not let apps set the setuid bit or change owners, so proot's fake root keeps them +/// in a `.proot-meta-file..meta` file next to the file. Without it, sudo and su run as the +/// user who started them and cannot become root. +fn write_setuid_metadata(root: &Path, entry: &tar::Entry) -> io::Result<()> { + let header = entry.header(); + let mode = header.mode().unwrap_or(0); + if header.entry_type() != EntryType::Regular || mode & 0o6000 == 0 { + return Ok(()); + } + let Some(dst) = destination(root, &entry.path()?) else { + return Ok(()); + }; + let (Some(dir), Some(name)) = (dst.parent(), dst.file_name()) else { + return Ok(()); + }; + let meta = dir.join(format!(".proot-meta-file.{}.meta", name.to_string_lossy())); + let (uid, gid) = (header.uid().unwrap_or(0), header.gid().unwrap_or(0)); + // proot reads the mode as octal digits written in decimal. + fs::write(meta, format!("{:o}\n{uid}\n{gid}\n", mode & 0o7777)) +} + /// Remove what an interrupted `install` left behind. Cheap when there is nothing to clean up. pub fn remove_partial(rootfs: &Path) -> io::Result<()> { remove_tree(&partial_dir(rootfs)) From e835fe9ed08b58e73721f743df5dcfe1600495ce Mon Sep 17 00:00:00 2001 From: mister-teddy Date: Sat, 31 Oct 2026 15:24:02 +0100 Subject: [PATCH 3/7] fix: offer keyboard shortcuts when a keyboard connects, and a black background behind the desktop --- src/android/backend/wayland/event_handler.rs | 2 +- src/android/dex/classes.dex | Bin 26952 -> 27856 bytes .../polarbear/KeyboardShortcutsPrompt.java | 16 +++++++-- .../java/app/polarbear/TrackpadControls.java | 31 ++++++++++++++++++ 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/android/backend/wayland/event_handler.rs b/src/android/backend/wayland/event_handler.rs index 2cb07d9..3ef2395 100644 --- a/src/android/backend/wayland/event_handler.rs +++ b/src/android/backend/wayland/event_handler.rs @@ -600,7 +600,7 @@ fn redraw(backend: &mut WaylandBackend) -> Result<(), String> { .render(&mut framebuffer, size, Transform::Flipped180) .map_err(|error| format!("Failed to render frame: {error:?}"))?; frame - .clear(Color32F::new(0.1, 0.0, 0.0, 1.0), &[damage]) + .clear(Color32F::new(0.0, 0.0, 0.0, 1.0), &[damage]) .map_err(|error| format!("Failed to clear frame: {error:?}"))?; draw_render_elements(&mut frame, 1.0, &elements, &[damage]) .map_err(|error| format!("Failed to draw render elements: {error:?}"))?; diff --git a/src/android/dex/classes.dex b/src/android/dex/classes.dex index efd3d07c5d74fa3729f37a03e1aa64c6aa16a8ea..5c35c3eca46b5dbe56ea12bf504aa323ac020942 100644 GIT binary patch literal 27856 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 QFMsQhx7*$N^Rn*$1-A1S=l}o! literal 26952 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 SXI1`Y9PeT4@(=eb^?w0!s8HDe diff --git a/src/android/java/app/polarbear/KeyboardShortcutsPrompt.java b/src/android/java/app/polarbear/KeyboardShortcutsPrompt.java index 7d8473e..05545ce 100644 --- a/src/android/java/app/polarbear/KeyboardShortcutsPrompt.java +++ b/src/android/java/app/polarbear/KeyboardShortcutsPrompt.java @@ -6,8 +6,8 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; -import android.content.res.Configuration; import android.provider.Settings; +import android.view.InputDevice; public final class KeyboardShortcutsPrompt { private static final String PREFS = "keyboard_shortcuts"; @@ -27,7 +27,7 @@ public void run() { private static void show(final Activity activity) { final SharedPreferences prefs = activity.getSharedPreferences(PREFS, Activity.MODE_PRIVATE); if (prefs.getBoolean(ANSWERED, false) - || activity.getResources().getConfiguration().keyboard != Configuration.KEYBOARD_QWERTY + || !hasPhysicalKeyboard() || isServiceEnabled(activity)) { return; } @@ -59,6 +59,18 @@ public void onClick(DialogInterface dialog, int which) { .show(); } + /** Unlike the configuration, this already knows about a keyboard that just connected. */ + private static boolean hasPhysicalKeyboard() { + for (int id : InputDevice.getDeviceIds()) { + InputDevice device = InputDevice.getDevice(id); + if (device != null && !device.isVirtual() + && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) { + return true; + } + } + return false; + } + private static boolean isServiceEnabled(Activity activity) { String enabled = Settings.Secure.getString( activity.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); diff --git a/src/android/java/app/polarbear/TrackpadControls.java b/src/android/java/app/polarbear/TrackpadControls.java index 5815c2c..baf0d85 100644 --- a/src/android/java/app/polarbear/TrackpadControls.java +++ b/src/android/java/app/polarbear/TrackpadControls.java @@ -43,6 +43,7 @@ final class TrackpadControls extends Dialog implements InputManager.InputDeviceL private KeyboardButton keyboard; /** A mouse is plugged in, so this window keeps focus to capture it. */ private boolean mouseAttached; + private boolean keyboardAttached; private boolean keyboardShown; /** Waiting for the window to take focus before the keyboard can come up. */ private boolean keyboardPending; @@ -113,6 +114,7 @@ public boolean onCapturedPointer(View view, MotionEvent event) { InputManager inputManager = (InputManager) getContext().getSystemService(Context.INPUT_SERVICE); inputManager.registerInputDeviceListener(this, null); updateMouse(); + updateKeyboard(); } if (Build.VERSION.SDK_INT >= 30) { @@ -181,16 +183,45 @@ public boolean dispatchKeyEvent(KeyEvent event) { @Override public void onInputDeviceAdded(int deviceId) { updateMouse(); + updateKeyboard(); } @Override public void onInputDeviceRemoved(int deviceId) { updateMouse(); + updateKeyboard(); } @Override public void onInputDeviceChanged(int deviceId) { updateMouse(); + updateKeyboard(); + } + + /** + * Android keeps shortcuts such as Alt+Tab for itself; only the keyboard accessibility service + * passes them on. Offer it as soon as a keyboard connects, not just when the app opens. + */ + private void updateKeyboard() { + boolean attached = false; + for (int id : InputDevice.getDeviceIds()) { + InputDevice device = InputDevice.getDevice(id); + if (device != null && !device.isVirtual() + && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) { + attached = true; + } + } + if (attached && !keyboardAttached) { + // By name: Google Play builds leave the service and its prompt out. + try { + Class.forName(getClass().getPackage().getName() + ".KeyboardShortcutsPrompt") + .getMethod("showIfNeeded", Activity.class) + .invoke(null, activity); + } catch (ReflectiveOperationException e) { + // Not in this build. + } + } + keyboardAttached = attached; } /** From 3b09bdcd2ccabba50ba1977a9ec954fd982f2819 Mon Sep 17 00:00:00 2001 From: mister-teddy Date: Sat, 31 Oct 2026 16:13:01 +0100 Subject: [PATCH 4/7] fix: shorter keyboard shortcuts prompt that says how to turn the service on --- src/android/dex/classes.dex | Bin 27856 -> 28040 bytes .../java/app/polarbear/ExternalDisplay.java | 12 +++--- .../polarbear/KeyboardShortcutsPrompt.java | 37 +++++++++++++----- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/android/dex/classes.dex b/src/android/dex/classes.dex index 5c35c3eca46b5dbe56ea12bf504aa323ac020942..88a4a2da238b900c2453dc769be5812aa8c9c345 100644 GIT binary patch literal 28040 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 QFMsQhx7*$N^Rn*$1-A1S=l}o! diff --git a/src/android/java/app/polarbear/ExternalDisplay.java b/src/android/java/app/polarbear/ExternalDisplay.java index 02733a6..9990b7f 100644 --- a/src/android/java/app/polarbear/ExternalDisplay.java +++ b/src/android/java/app/polarbear/ExternalDisplay.java @@ -151,13 +151,13 @@ protected void onCreate(Bundle savedInstanceState) { view.setOnGenericMotionListener(new View.OnGenericMotionListener() { @Override public boolean onGenericMotion(View view, MotionEvent event) { - return onPointer(event); + return onPointer(event, false); } }); view.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent event) { - return onPointer(event); + return onPointer(event, true); } }); setContentView(view); @@ -170,10 +170,12 @@ public boolean dispatchKeyEvent(KeyEvent event) { return true; } - private boolean onPointer(MotionEvent event) { + /** `pressing`: a touch event, which Android only sends while something is pressed. */ + private boolean onPointer(MotionEvent event, boolean pressing) { int buttons = event.getButtonState(); - // A touch screen or pen on the monitor clicks like a mouse. - if (!event.isFromSource(InputDevice.SOURCE_MOUSE)) { + // A touch screen or pen on the monitor clicks like a mouse, and so does a mouse that + // reports no button (a touchpad tapping, an injected click). + if (!event.isFromSource(InputDevice.SOURCE_MOUSE) || (pressing && buttons == 0)) { int action = event.getActionMasked(); buttons = action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL ? 0 : MotionEvent.BUTTON_PRIMARY; diff --git a/src/android/java/app/polarbear/KeyboardShortcutsPrompt.java b/src/android/java/app/polarbear/KeyboardShortcutsPrompt.java index 05545ce..161374b 100644 --- a/src/android/java/app/polarbear/KeyboardShortcutsPrompt.java +++ b/src/android/java/app/polarbear/KeyboardShortcutsPrompt.java @@ -6,12 +6,15 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.os.Bundle; import android.provider.Settings; import android.view.InputDevice; public final class KeyboardShortcutsPrompt { private static final String PREFS = "keyboard_shortcuts"; private static final String ANSWERED = "answered"; + /** "Not now" for as long as the app runs. */ + private static boolean notNow; private KeyboardShortcutsPrompt() {} @@ -27,6 +30,7 @@ public void run() { private static void show(final Activity activity) { final SharedPreferences prefs = activity.getSharedPreferences(PREFS, Activity.MODE_PRIVATE); if (prefs.getBoolean(ANSWERED, false) + || notNow || !hasPhysicalKeyboard() || isServiceEnabled(activity)) { return; @@ -34,26 +38,26 @@ private static void show(final Activity activity) { CharSequence app = activity.getApplicationInfo().loadLabel(activity.getPackageManager()); new AlertDialog.Builder(activity, android.R.style.Theme_DeviceDefault_Dialog_Alert) - .setTitle("Forward keyboard shortcuts?") + .setTitle("Use Alt+Tab on the desktop?") .setMessage( - "Android keeps some shortcuts of a physical keyboard for itself, such as Alt+Tab and the Meta key. " - + app + " can pass them to the Linux desktop with Android's accessibility service.\n\n" - + "When it is on, " + app + " receives the keys you press on a physical keyboard and uses them " - + "only while the desktop is on screen. It does not read the screen or other apps, and it does " - + "not store or send your keystrokes anywhere.\n\n" - + "You can turn it off at any time in Android's accessibility settings.") + "Android keeps Alt+Tab and the Meta key for itself. The " + app + + " keyboard service passes them to the desktop.\n\n" + + "In the settings that open:\n" + + "1. Tap Downloaded apps (or Installed apps)\n" + + "2. Tap " + app + " and switch it on\n" + + "3. Tap Allow") .setCancelable(false) .setPositiveButton("Turn on", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { prefs.edit().putBoolean(ANSWERED, true).apply(); - activity.startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)); + activity.startActivity(accessibilitySettings(activity)); } }) - .setNegativeButton("No thanks", new DialogInterface.OnClickListener() { + .setNegativeButton("Not now", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - prefs.edit().putBoolean(ANSWERED, true).apply(); + notNow = true; } }) .show(); @@ -71,6 +75,19 @@ private static boolean hasPhysicalKeyboard() { return false; } + /** + * Android's accessibility settings, asked to highlight our service. Apps cannot open the + * service's own page; Settings apps that don't know the highlight extras just ignore them. + */ + private static Intent accessibilitySettings(Activity activity) { + String key = new ComponentName(activity, KeyboardAccessibilityService.class).flattenToString(); + Bundle args = new Bundle(); + args.putString(":settings:fragment_args_key", key); + return new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS) + .putExtra(":settings:fragment_args_key", key) + .putExtra(":settings:show_fragment_args", args); + } + private static boolean isServiceEnabled(Activity activity) { String enabled = Settings.Secure.getString( activity.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); From d0fd7441f2845ceedeed4f23533e2171afe25d77 Mon Sep 17 00:00:00 2001 From: mister-teddy Date: Sat, 31 Oct 2026 17:35:52 +0100 Subject: [PATCH 5/7] fix: scrolling in X apps, by reporting relative pointer motion to Xwayland --- src/android/backend/wayland/compositor.rs | 11 ++++-- src/android/backend/wayland/event_handler.rs | 35 ++++++++++---------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/android/backend/wayland/compositor.rs b/src/android/backend/wayland/compositor.rs index ec02796..9d4490a 100644 --- a/src/android/backend/wayland/compositor.rs +++ b/src/android/backend/wayland/compositor.rs @@ -1,8 +1,8 @@ use super::bind::bind_socket; use smithay::{ backend::renderer::utils::on_commit_buffer_handler, - delegate_compositor, delegate_data_device, delegate_output, delegate_seat, delegate_shm, - delegate_xdg_shell, + delegate_compositor, delegate_data_device, delegate_output, delegate_relative_pointer, + delegate_seat, delegate_shm, delegate_xdg_shell, input::{keyboard::KeyboardHandle, touch::TouchHandle, Seat, SeatHandler, SeatState}, output::Output, reexports::{ @@ -17,6 +17,7 @@ use smithay::{ SurfaceAttributes, TraversalAction, }, output::OutputHandler, + relative_pointer::RelativePointerManagerState, selection::{ data_device::{ ClientDndGrabHandler, DataDeviceHandler, DataDeviceState, ServerDndGrabHandler, @@ -59,6 +60,10 @@ pub struct State { pub shm_state: ShmState, pub data_device_state: DataDeviceState, pub seat_state: SeatState, + /// Xwayland sends scrolling through its relative pointer device, and pointer motion through + /// it too only when the compositor reports relative motion. Without this, X apps see the + /// pointer switch devices around every scroll and GTK drops the scroll. + pub relative_pointer_state: RelativePointerManagerState, pub size: Size, /// What the client wants the cursor to look like. pub cursor_image: CursorImageStatus, @@ -191,6 +196,7 @@ delegate_shm!(State); delegate_seat!(State); delegate_data_device!(State); delegate_output!(State); +delegate_relative_pointer!(State); impl Compositor { pub fn build() -> Result> { @@ -218,6 +224,7 @@ impl Compositor { shm_state: ShmState::new::(&dh, vec![]), data_device_state: DataDeviceState::new::(&dh), seat_state, + relative_pointer_state: RelativePointerManagerState::new::(&dh), size: (1920, 1080).into(), cursor_image: CursorImageStatus::default_named(), }; diff --git a/src/android/backend/wayland/event_handler.rs b/src/android/backend/wayland/event_handler.rs index 3ef2395..35677e3 100644 --- a/src/android/backend/wayland/event_handler.rs +++ b/src/android/backend/wayland/event_handler.rs @@ -75,15 +75,26 @@ fn emit_pointer_motion( let state = &mut compositor.state; if let Some(focus) = pointer_focus(state) { let serial = SERIAL_COUNTER.next_serial(); + let delta = Point::from((x, y)) - pointer.current_location(); pointer.motion( state, - Some(focus), + Some(focus.clone()), &pointer::MotionEvent { location: (x, y).into(), serial, time, }, ); + // In the same frame, so Xwayland moves its relative pointer device (see `State`). + pointer.relative_motion( + state, + Some(focus), + &pointer::RelativeMotionEvent { + delta, + delta_unaccel: delta, + utime: time as u64 * 1000, + }, + ); pointer.frame(state); } } @@ -293,22 +304,12 @@ pub fn handle(event: CentralizedEvent, backend: &mut WaylandBackend, event_loop: } } InputEvent::PointerMotionAbsolute { event, .. } => { - let compositor = &mut backend.compositor; - let pointer = compositor.pointer.clone(); - let serial = SERIAL_COUNTER.next_serial(); - - if let Some(surface) = get_surface(&compositor.state) { - pointer.motion( - &mut compositor.state, - Some((surface.wl_surface().clone(), (0f64, 0f64).into())), - &pointer::MotionEvent { - location: (event.x(), event.y()).into(), - serial, - time: event.time_msec(), - }, - ); - } - pointer.frame(&mut compositor.state); + emit_pointer_motion( + &mut backend.compositor, + event.x(), + event.y(), + event.time_msec(), + ); } InputEvent::PointerButton { event, .. } => { let serial = SERIAL_COUNTER.next_serial(); From 400c20282dc1fc1eb90df39dc122847158d97088 Mon Sep 17 00:00:00 2001 From: mister-teddy Date: Sat, 31 Oct 2026 18:10:00 +0100 Subject: [PATCH 6/7] fix: same gap to the trackpad border above and beside the logo and the buttons --- src/android/dex/classes.dex | Bin 28040 -> 28808 bytes .../java/app/polarbear/TrackpadControls.java | 61 +++++++++++++++--- 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/android/dex/classes.dex b/src/android/dex/classes.dex index 88a4a2da238b900c2453dc769be5812aa8c9c345..4a37509e34f57e37fa77dabff46c104cd50d5b4b 100644 GIT binary patch literal 28808 zcmbWA3t(JTwfFa#nLL{&(@D}tXw%a)KuTX}(??2DnpYob(l$+|rA-R$Br|EJP0vhb zCT(g_%F6V+V8#B-g}*sG#nYuudZ3FZvD|U%b)vPQz-TIEN`_A9lX_!mhrg>Z11QY9TqrEI0PtRgS) zckoY8x>~7)paXP+UN8XmgX7>U;8E}b_%(PL{0Y1U{sI08-UPliO637Rm8;cm#KEoLKJXxT4p?iIsst-Q0(<~` z9{e1rHl;2AZ6FSA0bc@-ftP`|U8xzM0qg+p03QP_Jzmzh-PH-4}7CZue1^y0lw=ni#0q6mP;5u+8H~}69FMwCT z-@qHd+Df}X089Z@U;$VL8o?^?K5z`&2Yvt^15bjVf?tB)fIoo0gZ~C^g6wXk#(^R* z5tM_O;3BXPECtI!6Icb>!3NL;y20h359|UCNP_*~Ah-?O4ZZ=M1iu8o1OEWt9?Asg zfeS$mSOMC=rJxVQz(H^r+y*`e?g!rikAk0oXF<`7r`R)h7R3v37dU=K)wE5Qxm zR`3b%S@31>E${^R8TcLeBX|w`56HWWIS!_Sd0-I;f*rsC2f!h43-~bjH24De4tNUu z8vGT!35qUf?7(cW0_CwR57Rmi@-{-8AL!F zTnVlRw}H=quYhlZC&2UIPv8xZw}Uo;i@*}F5?l&GU;w-eybs(4J`6q&9tJ-EPk^Vu z3*cq&8t{aaDg+ZjC8!2XU>(>3dO-?Y2W|zQ1YZVEfS-b2fj@xPK~^tq1XI8Tpc*U& zEubBAg6&`s90Iq1d%<_W&%g`dA7Ff+QWan^SPQzqWndq;3fuxd1?~sm0FQwe!0Vte z%-jSQf@)9)Hh?X_0SCbi;CpdNIB9?%QoU_ZD5 zya(I@ZU>(M-vp0AA#q= zE8wr-@8G{cp~9YlDPT631LlHxU_Ph@3&2890~UeBUDW-yA2o*!3=u$-&s3_PC_J9Gf7dRjW;$RRY zKoX?D5ZDLygJEz0yaOBrSAchdcY!OxA#fGA8e9Xe1=oRhgTvr@a09pzya(I_-V5Fb zZU#rdE#Up&R&X2m05}Rh2yO=-0v`q+0Urf-fRBOW;34pz;BoK-coO^w6oT}p$S}7v zKmmBmgo_P-C-M@*i%zNG?}ndf_~Y3l#|E-q4k4F^8O6D@FK5-%KA(9m8?Vg zh+OzJ=t$jSpu^}3FLE0i2lBB&QW!p>FZ>pxpMu|!!Kd?-^}GxDaj2|o`FsHgFZ2J4 zhL5B3CBqNHpD_Hj@b?@3Hux_a{!aL>7+%Kcq~T@leAV#B(fOL;AAtY5;UCQ4Pokfe zKZ^XDMn}f_+lK#f2LEh^&WrHrw6A37yp|z<1O5S%kB@;(>-#hK$r-$z!B@ej(^kWO z!{k$+q0^qhuc0mJItMf4M>6EwGUVY5ejtNSW$;(PpEBiMn<3wo(QmgQ|CZ7DXok-9 z89IkD_~8tGO9sCqgTI@6(rq~of7;}KG9&*}@age-I79w;2LE&h|6B$y>mr^1OUS=t z@_!XRUDg{J`lmAL){()>JWuB%w(&ue&*SjlHN0u5~UJw7M;alL-X{GIHoet#be5Ac;9cgcxm-eQ0 zo`ZkP}P(YN`K%wjke1ywIl%{V8+} z;WdV?g|-pyfo@=j{Sma26VmsgU50K_Gx?D-QjIF7ZM?sp+tCkoRzslJd40dYPe@8@k=l9fpReZyD)@9y4?m=|gIo zx)IvP?)oR>ee9Y$ba+^Wpkef7&lW2CZ^Y>Ln|Af99qP}>`#C}WMblo@MY?`waL~|A zhHf_WQbWUR_tyA@y zR;v}7zEgcv(^mBrO@rz*G@BEyl$XuP=94;{ul_^Raq2!pzii~+)O5W17BrU;mvp%% zeJ=I-Zyi3Y{;6r78q#`))Vnop(&_RzO-s5wdRXG~nK2U1=TtC`aDi!80lh5kDd1GM zSW~w>MVxF!zlhPe8F>*WMxlPn|08lgE9HHJCvcL=()9hR7#d(bY}0h9x(r&XCaZ~> zR;uaHNvxxPkbV;D$E6-sq2*a>p{5?S96FhqD&Q@##gsw_=cE03nDlvmSj%BN|Q z`kSKof5IThseVP2GaSM@rg?BMvW6%q@p}b$ykor$(m6>N% z+#-$;p2f_U@@BJ|XA_>o$!!^Q9y4bpbUr8Va!ok{n(z{6H94QB>1}GNrXDp*)2r1+ zO|MZ8K^Ks|P}3YW!-T60U1aD|=tA_0G__Q~giADa(=B9BW}5g)6Mw#@oGA@$()1eb z_pQMK))8L9P3$+cZwW2GnDA0Fzn9YT3v}2~U(__FzNBftx*xjS=q=|4k*C8=>I0fy zp^j;qRQEzxFh0K}f1$5wI$urD@**`^(~Hy;P3Nf#H62nP*Yt9Ar>3py9!=M(&p{jL zht<$VZgri|Ci0ba*#i9xbS03}+}X{$N`UB^iDYU-}*^=7`Tr~F~$LJt`F4(J9XQr-siK0>&| z=nIwbMiUk);Z9DzqTk7_^a{dVCfr5%I>JF?M}pi~pF=N5I;LW*C72c zO;cv9rmo!ya_hZDhYxAH7o`24)?v3jL2l&V)8VLk*w9CynXYKv`PIRP1mUBHQlLR&~&|e)x=9X zH?b4%*7ez`228#7YPw#1$Iu6j{Bh`JQ*WVC@6F8I1zLWs+NJ3swMWx^DsJ@m8+u4n zcOR1U*Xr;(^;PJlW?U~tF5|YvgVUMhw}Y_s?+!D6c8H}ze}~zxcCg-#>aaU+c9?l1>16$d%zhYReOBnOyKjW> zz--XrE$X1Ad({`9y=MLOVp%WJ;R@BDX_IQ#^lFvVbb;EZ=`HFeBbRmBYu0HmH&($7iyIZfSh>o@DapMCXzwLGH6Q*OU$PrqqTzq(58)bceds;O%i`mtN@ z)Zte3E<>->^m=u*rtW!0%72dzyYr!+_J7F4OTYA+dC^ZleO8Cv{^`e?_gxcy!q6w7 zd+}lxXxgaeXj-r4Lmf_deVTgJhoLdbk$D=!^I1wb&N`k4P2#Eh8}gKiPqEYdL5CX* zU9NtB-Vo=f%c1-5n687aM@K%|kI{_396s)_o_1?7qv?JmUhX#%e~9>$iSIZ^{O!aa zHt`$J5q~%FA2jiu8S!$*mHbZ<{}~hCl@Y%qBmQe9K1jUWd*n_ecWp`kFzJ72;x`eN zbGy`EJ`(>l@qah*n+ekmoVVT2G(!`6@R9CyeY_${w_om0FOmL}#LH)kOLTmk@+DpZ z_nY{w=ZNw7iTL-EK5~xq*AxFelfFMA{Sj0D+lhb1 z#P^!`bUmf~yNOSbw|p%9gMWU7+`U*EemO-IV$D1{-ZAn)?Qt8Gr;y9MDgX&_Pmj_QmT}Dnz$@zy!t=r`FwUHcWy|T zKQM8F{);SbHWTEIGoDmp>z^cU1Jv>N^DH8t`>e-y?7iGr^Q}Oj*2+`01zC)jS5ekvRSY=?bib9aA$#;s$Mpdt(9l z3~|q_@OG-W{R!@;j-AxuF2bME;S^!V{yaI#JjiAgq#OQ+%9KN;EKBn7SI`Eb^E6HP z<5CAtXH5W|&x#IIEPlQk?D&pqT0FYm^Bc}y?|x*`4};q4E_2)cOhPX6p_e91-__ zkYb|vPo$62cJq96Gwmi(`Xg;}`{w(EC58JLm46Sxx#SOOc@8T>*2b&`H=q4E-_>#% zSsB|JBOeStr2GqIoLu`+k6g|Q6`a%K{yX3tdmM36l8lMin5G7am+>jb$54yDaIU3_ zTIG7&YFRlleu2L@c7d|tZLFtg+Gu`;)KSYO*L1n-$8xYf(3A68Ckf zo1Vv({aD5*1pZo;C1K{Zw`PIlIjH^fZvGWkQ1yDJVYQC2+N4!(8;_$e{nSss z#OfU)NN!f9LM&Ub(&WbcrDd%W--WPYf{xc>xww~kUv-jQlZ?_U+c+jC`aHgZ}6EF3@%=eMb5baTg-3 zV5Us9Hmi7bxoTjpOczaG1t+Gse4ngzh~A~$_`Y0feQEvd}VBo zPWAbCkPCbh=m@vRc#cE2~!YMyNk z+MD$HuCQpO$8F^TR>E{r6?ki9mDsDTYSwVAmwbzv|K1|6v_b0n=Y})KHZ7O7$(RrN zf5mqpHr1`e-;f_dQl_?80n4e*RoNEx@VufD)fXw-%J(euBvha0Lbc4}U@x9WH{Vn1 z*+ot-=rvl)JgwT{__GKVP|qBpjO!dd>J{E>RpNWX{D5?YoebN&pOr;H|utVx&D#_FI%+t1!FRGtyY33w|r<7di~ zU6y?x;~Y>SPeHc$%j16WIFwnRKstfv4+Ytdzl7Qr@{_jB;~LZU`%-4jQ;fu@ZBCyX zC!ph=8)e3&*PXj2Cz`mRRYeb#vu<5GV^Q+dk#w_0(s?OaY{v}ZWW5Cgvs94vGw7dA zl6>)fFe7YQTF49$ZPoy~vL^bZjvC}uBSN%_w@tVjEbBI>Rz2`b;J}1V* zXu2A+wMLoMW;ymuq;g+ctAY#WQ2$Csq=22%slG{{S<`(Q9I4A(#=TV8^mi6LChPPv z;+mlWzhl3KBxX+`9AKvY4eI#+f~){9;0#i(R*lSDX7O2372vjN~9(V9}s` zzSW>AeC?$AvrZMa-^aSFV6Qh4PQv+E{NRGQD&QHczQD2=qnpuj?7vf*%#rk1#;KRs z+_=4u({hD}Q;I)sub|dFfdy)7aW!MIfIU5jw2iDrH9 zc5K;&|4S(Iu8ErDlT3c;U{$EKyKyt&kBXyS1^Z7t1L%bwiHbtZdc!cr|{s zUreEt1jY|ME+ zm0gRKEgkoGL-1KNaltmE?}zBK=MLptXw-GWl@sfJSDkw$5uU987$Bdx1&d&(VCX6ue(E1;J6 zC*u7q_1KMWPaL8r7^9|APluMcA*`sc>g6+gx-@&ZG5AyZ$gAjA`ZnuOW6ldwPY+{y zF(WJX%Uw_RGT$aJZ%cUNFjsB0C+W8yc-`He$Shus6^~cn$oB+~dmckoogfWCW_Zy^ zK3_$q&ny0T_3O+zSsQt1iR=_{;!o*ih7VTH=l#b5wP|n>YpRyr|L0b&kA2e$CTrBe zlH*r78`rAllHgzsx68?_r$v$T72Ckc8`TTtH{qD^j|{m z`Ps{}xhJw#y4aJ2maywTot0%({qAYYvZ`LJxyDO-Z?ST{UsyA^rCL>&6y#3eFeUe8 zu`fTNt_@IG$8t}$k&1srDYSi%dHT`jkenJTAxy0Xz+Md|{p)+$T$ z+-+NR>YkZ3DhNu{jW^!-X0OG$q`HkK*MZ_~GZ(35weg?H_p^xccpM_TUq3oNg; z2jhf}*n@g$NiIZ;=*(MWmci?wRwaJc%C?c^^Wzh zEaud7;?E;KU+hY;h4*p%a47|z0(`{01y2br(_-NJbnFD#;!yLh&k?|rD+v7;`x;AxM? zs`?{IDKD(|#bPf$IbY4fQV}w|tQB^yzzJKCu)Z=HD(ETr?CSxY|hqzzf&T!AP z9>OwaT5ey=n4K*jv=?i6+GiL_>sQxX;xm-nrR1{uaVcO4$@O{Me*ca2ni_yD{{h&gHM&sH2 z{N6I}XBfG9%j2!!7p~&=uhmR+|3`Gay`#GJ|1ukUX4bJQ)X}qeuSv7el2C=kQ+4~d z%nQfPwK#7hbNp}8Qw84X-Zp9})_fK=Ki}HAU^4Mio12LjE@-1Y5_0Tiw0nn_wsE&y zJ`TdA3wCda~S>yEXzOu1w2mdSmc{b~bg;{HqM!MJ}p-)^ql3B1@{ zO0M9kf872(bHcITug7)tE_4^;B5PaH%eOq6mU$r-2UgwtHt{F^MSR-MJV5+X@`>4H zjCZZqR-5bw_U5S>GkGGdbI0HDUx}`))BT#(Qrjx8?hpSkl3H)E+B9=Hl)Egu@^U=} z6}-g|xBsBq=fi4A{nO?DfbyFO|I+Kl9$iBnidkwhmt@wxlU9nK`&~i_SLjkRtwgDH zO7A#gDdZltbwP=?8glpCT3u>d!5+Zq*+M4 zR!;E!wd#2M#Gw{S74NQC4Dn5Rson3XzdTb7cGNMN-D*?E3RNQ>$KbZjD`?Rita1VI z(grxI%;VlRXTVSAmvDomk^J(k9V)n4(p&h1<+Lqx z+}%BMm>?1kV&5JAB+3h7+a3Qukj4Dxp(STOvC!zDD|?G$A7EZp_*nJ4Ng=DAdq0*) zQ*6e2*+m@t9q1Ht<`!R)rKXTJN?P$Q$n)&9r!3$LlrAnlPqpCW5^ov)A-uPER`8Z_ ze$H8zBX)J0K1&BKtl7uDT;lr5YJH7r)1i9&zROwVuD3|~d?h_0^QBRCKj^-yhOq~{x;f+pTNID(=zLnHM8rWnwp)Z)@6I~12kBEu_IS;p6{GQq`koo6`+zycsq~Oo;#*XQ%enJo!ltWM z@a`t3(5u{7?D|NmgQP0Nt`^E$kA?ajltOvo6Lzu0)T+X0_mftMzy9>G1H6M)$jPWM zI^51-u8^0(3ZwVexACG1-B8GTuae?UH*kjz+`te>)IAca=gmS=X%~C%=6v=4OiA z07O|$k#nOkYf!jZ9aM$g-SgUaw{_3c&G8hyTi(ttJi@M7*f#y}QEPJ1^!mBeKX_UB z(e9)5p5bZbbJ_k>VR>6y+p4{ugLiTGD(q%vyPYDXwbiS61OuvY`EYypv9`9kNG6g- zQL%0&N1>{h7PYB`x>pv<_nRl=t)xPEMZEAtclU5xziNSNJ3L&~B=0E~mbdOc!Jrn( z%gKeq4@M83K5We@+IaM9PQ52QP3M1;x>U00=Bt}0aj?C7J*gaB%pMV3DeU26!*A}E zmnaK)b4|W^PpL3!P4Le>eN27cD=KsA)%{+iZS`$!hueDS^k{v(y1^%LwKvWk9#*&b zh<51%-3^zc#j4-c}adP9g?@ojC%L}xH>O$QOUz8RP_tdLv zwOZS7_oXY!%a4|qM?c?jLcJ_P#mTj>UK)9!J2^j1IQr+d?(TZ^gw#Ab{Yk6XFR$BD zPJ7$o_MY|=-AB7`Ix+m&H@k;>qC2{~Pq)=yvfDHMF5cCa%<6e})TQ4x^u0otpFVwk zbh)SCF7+gbaE7g??fGNT=q1tidi4=@ndhxi{|kpte)gz3E)~0dZd>{3?z#2LyLZn$ z+TFJMlJ0KL&S|QFgjDTCokcyJuO3Ia{7ELyl%_q_?4o1q&YLk;{Z0$p4}rop?mFv*)~ zBA0b2bO1=a+@K^ZryB_;Rh6N`hPuxoBp=y#B>g0K_s7jk`~s3kC6GJ{2nk)T8nt{4 zyo_i8rHP)P-_RsfbYtoYO~;vf#*93Pt&n*6Xv5>Ox*mq*rj{8uP5T`rPRfzoC0)A4 zl9$EPQHgWqQm=Hqq4I{3=*#cb$k_Gk9MZC3Et8x?XS9D^Kfy&N&xIzB zh0HFu?DV`?sM7~A+v$1N4_|JFoUonTA5Hb`vZE+KtrJ~MG(F&1iJmOrO zzrfA6CAQxgh=szDupNrWlXg`sVYfQTRA^wph-T$4bXAqNYSv~od0v~TLf5WXBGosP zN_He-d*dl}-o@#1F78i+cJ7ThsqLY}&g6FLtfr1eCU?d5Z$Cpr<<|ENM4e~~Z)Lqp za~s!pUAnS!WsAyd>|EWny_Sz!+USH6v1piHh)~zYfz<3^s8{!yw0m776_O6J`)Ncp zm5dDZQ*$CzR#PavGop%H+@9%3M3U4ll#0e2RkCt86-hXuftF}8J`g%EZ+B>4NLj0t zXH~26t!iy=SKif)9m;A|o>ru-xGh`t7cp6_>$_G4Reoz<%xM}*rO1z?b#B|rtxX#m zJ6qsPXQ`?*XNUUwBFSX5H#!ha9T*u$GH@b&sYqDONNYA`Dt5W!?bfa^tzycYnhuW{ z6VsM<|>AD!Pw8s^f3|jMPHp1Cd0kB^nxt?W}AXiVlP$ zi8?jqU#Zlok~5;*gm@OtkY-MaIyHSHj$uhLRpvJ{#bXZK`q17;W`#x-v|?u@85`Jl zW`)jB8BW!y1!raNR;raO68)jRNaY5nc_7-ir#+gabjfYB2hzEXNm8dKpOvfR=TP27 zXT@~w3MC?88r>gBL>%U0<;rk06{Db;Z?9XYD$i1i@15Tmk5gy8sNB&!J1ew0LVbHe zJ0t5t4&Uc2kHtxMJN?m}LkXSRxyD13q_2k(RK8BxBNf`22*r0r`;zmUqN%;1c%_?D z=KL6=an7jbkh3pDzN39QlBzj2ApJD;>=HXdQ72W|9E#VeY5yvAZ7k~4sq%jn-IY2p z!1Ovtu{v`+VCP7Q8IB}chWh&}*Tur;PTh%VSyiX1&rTgqg!YGe2O{$u!=X6qEh46@ z#R%)vthY#^dty{|bW)7;)kx-}TFXW74UE?1ZIafh zbCeQw;zOzVt@52NK?hnQvX73Ia{k#BJUeNo`G_&s#0phzUfJ2zx?z2tDjvx~$2nmc z(@`sga1HhhD>g>j+7xxPF*_rJ_82=hs-r_6PsH{`QJmjpHulP{NZ(L`LPqD^P%1hw z(xZ`7f+bO>0wWdE(e1IFnKpM{G_pUPB(s59p3zrzYSKtPI*#3E^eF})Y#KGHK57GX%}oAdjQi|!=DAudf3(l- z!?(*Z7#r%_HL5f^fkuiwt23o8s}r%II1BecY$(+cN`&?@NlMSGjoS^GCRS&e**lfN z192wbdFM*#_TO2BXC}u)rxGy^oJ{gX=gMPrz3vMQM8n1~tQ_u(V6kNSj9P|_x}{T{ z*}74?eYWaZ)8ULW&zXlEGpXmDSzm1__C`{>V&VBsp=3m_8rmmEJ6+Q;6FO7h-GSY$ zb9BenwaipIE6Xu6@+@7B#Z<@iM&@B)f0RRUYJPjviG&hvFO8WBqvqrsx|yriLzDQosM7WYn4C@>{n2 z9Hr^g&08g^Q**}Tnm+BH`}ECXI3v5X_L#eYwDR9orDgxFDx-4YzgGdhqn%OHU;=9% z4~30ENJCy1lIqm3mtp=E~dZEUr@*yj^|tRkKcE)8D$@qgO_4cHUY!J%;Xm z@9fbytLNC-W1IC3$C7^QygNeSI#u;=Xu0`u)PBqSq#MJbfql_E^Fxjkb8p%6S2}$I zu_R82<^dcQa?N-P9oJ&XG5KtzR%~g)WHRF%@#`XcdyRrz45pn!r7ODA3CVUX2W`3S z&mRamJLflZs_cpk4#|Cp*FiF4+&cR+MC(GSU2>ipO}?RbH%49#f1@&U*^%xY6?R7Y z2V^_g7umqMPTz1cwz85;%^}B;v-z0?b#a(;IJiy8&~#-Zm*LEMbsgXixmQj>qgCME zCsmlA?r48|tPcl>>m6xau;2i00yBYB&iYu&c5psrb#$&=)up`57nQ@8Ob3fmLnRoIxcek!p7J+W`yV0WzN#89Vm~KhHSGTaD z1A1E3q@FWR9Of{@6zRzrV6#AqGi4(aG*g(KO!@9nr?R@5_x26H0DjykpBcd`N+x1q zMuLUBJeQW&WXKnt!H?=J&X6z8NV_CMzBGfc&ES{8o64;k2<=R&Qny0Jua>?Usoaq9 zBB31)r>_i%!(lgF4xFCKS)qaWu8^8%Tut-Rm&|$Td5CW#vNM)AFpq`MwQ@c4p4*W8%n6`-WYDhy(+(NfTNUKFd$~R0)Jc=-y+#5|Mm0wyE!pEGxxalZye)5-Q-OIQ+E-Ftru6%NhM}_akL?2ng zm~u4V98FL;l`C>NOset1bxKpj@1T4Ud`qf4qHS4t`x)c3-FOm{DnBAuNV!SSnEps4 zEFB_2{>m&ju~o6Yp`^;{*JE7-N#WZH#@zXkFR~QYwRb2bm5|_YG`Tr6Fhs_Lnqn~q!BK_6Yj1CtSUZ(VSg$zc z7cSFxr3;et3~fvM26bLqHk{I>v_$%12|WiVkiI3-9~v6K!KvNRx)rXE!sv>-qBTs1 ziLOzSx*EPUtaL7B9NnwG8aJxowoKn&f>F9mx@k=$x^q`bH*5_)#~Am{(7={4;jJ0r z16|Q~n56nLu8PUUz3WNK-0PgKA|ug^TS^Z>`WT~!RmLRqZl?Qx9n;!%c<6e$8#va@PPxjs+Z9IIA*sXD#NaaN6v|+gX83Dac)%dhzo#dg4(m~^I zQd3+3yPoN17NL2ZRAQoBml}s$XD7Wm@eC;^KbGtPu7PU|^XyXjN5bg_j!HTsdt>{gX|7*e6~&x0Tx%*2b97Ozm31?usTs0# zre{ji8IKcPT$za>o`UAbWC-1!p>ghbQSc;aS0v?&$C4PbI5uu1aX!mGy@}<*!pBQknrBr=<4NH}CwK|-!i$&|ZSmzt1ixtR{C#E4Bp zF}d8tR1Vc-+li>Ggx-k?68a+1<=!K*+;fpPF~lsXP(O=_IKW@T{jVGN5B}iP29rRAHK16&;{Elw`$Jlx>_j z*q4ZEJ89Ik*xkQTTNO1Mp?j8@BqBMyNC~a(Kx>84J1`b5H+X~z*UvXMweX8DMtR24`V z)3ql;bM$DsA$iQEYvBgnO;tu$KVVbB1*0kEf{*QCKiTlz+P|JtCx&9YghsYY=2l7$ z=;Jj>Ka(aQuUJSN59}yuC?02t#budJT?UoYO|5&bd5?;(DJhbZL5?sc~-DHBkj@=anOOr925?I{G;Y zz;gbzc-Q3sf4KEs?`o+%SRrR~)_ldq72(Km#hi*h9*;+UN%%6(L1!0C=MiZ z*}(huB!~7UD=zL24J0FTD%cR`hT_qRix)1KQ<2;in!A7`q25rachQn%y^ABg;YH!_ z!rtCx3wlG{rJ&n}p(Ivmq>V z-)jxGTh>jyKztWC*~p8%)|4amMc`* z?p67^el1^j%GXKxdR)F!l7F0zSIY@;$e~@*UqO?W{OrKc(B%`BTf1|8~i; zo|f>_ozGa-tGobXSu>B=))D(>BE6~W=azMEmzP&+&o8rR=CjhY6Pu~tqw@7?kgwOe zto$QEEC2dUR2ICr*~)uuixgIM#QwEpbNklcSk{THBARu?{;h}}-}(}xr9~GVv0oO^ z$?o4-*6rP*HT#JDdl5a@^9r4=MRShWe-P1gJ%6NKJ)$-Di2WxKy|V4Emhbw@_-!t0 z-ehZXHNScAy2$Um{0+;;f6L?bSPMP;orJ)R9;7cte4Yow@Addz@26X>#iY1dhT@lEg3Hj+%A$=c7D|3yK9%qFU_qbwZ$(YWc&o~5S<@8^uSFqzMkI0*ImNBD%?#2 z5|pn~5_~`+ejM|9Zg;HQN8?^IPFbE;2R!+A?elmZ*zfhcG3@cY_72N){GjEz^PQjZ zSg*twq=6jYy@QtZV9K&y1lJCEthC z<+D8e)eQOl2>zxxFZh2P$UA)ZKt=XNBNP6s8T_dX{(Bj`;d z8xszX3Cr(W$|qe+x@>aQcy4GRqo&E;}<;mRRsPHA*R`bT*{LF%g+ZdzjrC~Ouz?;HA8G d)4%*;8n4~EW1o#&c=>yn{Gy$%|4CjD{=Zbg*PH+V literal 28040 zcmbWA3t&{$wfFa#nLG#~8772BgfIk@pg;lv2_!%Q2@=91j{zirFquqXAUQLcnLzMW zv?^4&Ep3&e)z-GyYHM5Da$9O`tHr8qz1Gqfx7uE{_LkmSuWfDZ{c5k@Z|$>BG6|^t zzQO;jwbx#I?e}Y)ld&s0kXKo?P~BJl;^dE=yz%dC+xD&b-z8Hf-P3pVtXGZ~{P1wE zQVFHhz@`NOi2j`FQR@4aBfkd8@f@X<?(S)G}y;9}q7oCJ_!zR%+aOrP5Q8H4p}8 zz|X-SK|!NZv%%$H8CVT&0EfXn;49#(;9>A6I1QczKLF2ypMvMXIq-Y%7w{ICwMwbA zU>mp^TnBCdw}B6U+rgdSW3-40sZ}2(s2FH5IgiYr!YMFF|gzQY*n8a0~b-_!4*$JP*`b(t#?_ z40eHo;BIgVJPY0c#p{%s2RgwA!B@erK`!k-1vG%I;5u*@cnrJ&$~Guf2l~J#z*FFL zFujGk02|x_9so~+{|13prJBG&@UP%!z}Kc!1?T`bgD-$H;IE*ho&E)#;8yT8a1IoO zlv)fT;AZducm})*#%`po!5Xj~#KC*Paqt~*7Q6y-HYs%xSOl6tJJ<#yU^nOk2f#t_ z0dO4L3+@M>2Y&(in`u8VA1nlm!7|VY)`1Y%4#J=t^nxVV2d)D*gImD|!JXh^-~@O8 zd>(uSd=)$l9s}P6-v>VgKLx)4zXC6Tzk|Xnm6`z-gGSH_t^#|&K5!#=Ke!8g8hjai z3w$5^47>zh1%C&DElN!Wvp^N71wG&n@Nw`N@CEQNcnmxNo&moAFN0UX8^GH^JAqO# z6D$HXpdPFNo4`&G2mRm<@Coom@Gy7`oB_{)--15@?^dNIfJ?vvPzPE-2j~F@!4Ys5 z_&hiTo&aaS3*gT{ZDSn)lfW#n7&L(ph=G1^3%CQ^3%&?Wfycqm!EeDEAZI&c0F;3W z&oe(*)`E$}!v z1AYb0fj@)%PWlTh0_#B+=mlwTBX}QpKez{c3VarP9Xt!31OEkH2fhgNA6y0+Knv&u zz2JIqD>w!o0N(?@1%C$<2f2+rcNnm%wA-2jHjRIq*C1H}DoHh_b(d zi@;@|1~h~1APV9j4c-Ip0$&G@fggcifmgsA;D3SDO={70sb&3bhOD1~IS)>;*lb7uX;U5}*$xK?1WbArzB!&@di*6)*lgdeRrhtD^>*eL{JQ|t);at1Huk+f1D z`CJTye+79NRO-qEoIphrcv~pKW-tJqJjeOPMP{zOge8UOr-{N^=e`b4@-Xm-!|iX`f|aoJqS} zgnUE?)A}Pk(fT9hT?Hm1m;qe_W|*|C@bVFz9iY<4BO>G@a^XcTyv!fBefGf1N94j+ zqa$VO0d+=Sc#%taWUk3a_=M&hec@Ld{XY1X4BoYwLVhK3nFBH}y|wC-83C=QDI(&XB*7!M~otznQ`NR&s`R^M(Ja;U~ho_RBN)5M_4D zR+%BcJ40TbA+O8eS7-1o8T^&-UpM*g%#f!u+9!ehl+hW;(78QB=ST*BQwHCa!S`hF zH(|%E%MtiTjQ!g)?B5OV_REP3`GXn!$qfFH3|_{+YyWZN-!S%{hIjKio1y=7M%lJy z@G_oVJC7j$rm^!p{I?AM0(QO)FME%C9tFb7_gm=?Q-Cdp8$Xz>9Usp%!>+(`xSLYS@Cyh<1qs)ETPvtWW z><7{&K6a@$;f4Og(6==8sIVpStJT%eal{{hPT=3iiJ!Ho**1Db~X*tvFf|TZzevMc%dKFv|sH+ zekH52Vov%&Pe$@^QqbGZ0r-_f4 zdPF#JokJesB=BAlfPRKBJY)C^6F)n%G4QWcucR`WI8ul_~TMs>fYE$SpRo1T*VvQ@tNuny;` zk7+tuecaH`82J}99itwC=5kV%bh##dF6DYnhi_1S)O5SrrS+~>1DY<==|*v~m2{(M zFNx3Nlq=ypIYAT7H}%SA97}!j8Moz{I`t{!v?=<9oNR7DUdV|}sGt1*6S<$6aFFm= zPB{OE@K{chxr76Z`}LYusSVI#dVRE})6_WVct+A+(I3wV{S8f5sK05tN&Sze%hX}5 z=TYz1v`&4%&~HIYm;q8Qp||UByZW4_A@!i9%hl&K-Kf3*oy-gp{VAlFM!3|ZXSAum zAfJkSGxDk25I&^oYIO&68YkjnO*_Zgy)jp zt7(oJZ^BayooVQ$&MOf>P6Onix^+`9~2sOfd89$LvtH5XdN zjQkJkS4GX|5ME%$=K^Yekq+-xpVBm@PH4JQeHyyN=q=$U_`jrILc86f<@?kzO?%bd z&|3QE1>%MNUQ=35%L~;QO=qeCO)piGHI1vInqH|ssA;3RQ`1%IqtKWLx9aM`5U%{>E%h1)FSVg`X$vuRdO!=EQX}*ZOiQCuBT7I=U zu4z==t7*6Tgr<$^KIj^HETpM3FPqJHX{H=|kPF>wXb*HPDJ0*u=p7-v&gcu3@Ol## zD&Y;>7)5^rH$9thiwU<7-cPvItOKpw0{s5!QSF7!sCRC@-b4>bew$2qvx(nKI%&7f+|;F=HnZ|dI|==zrq`;= zNxvBnz)DTm=t*l%T!rM%En<@8p zJQHGXJ7H`|4PUaMAU z+NOFn-K9PW-C^e64p!l*7P=Yy^+iO+`+j`=I0K)FfuQ9;E8eU zU9BD@VAVqZ1k?-+U-x(5%SrRh=?)U;YnhwkM> zwoy~Bx((VxJ~A$Q*pc!G_cCwGpb5NPuORO;@qKs>f1|^-hAvi*pqJ#l(*jN5ZLEVf zqaz>jAB5`Ve1xsN`0;j$R6oknRN}Xp_=Cj1$HcGGu}*q7{y6a;GV$v#5Pv`MpEU6s zGUB_8{Zqt$*~GVG#P?*xf7isf67L72K-$DVMf$TQz76?UAmx{j)bA|u)@~hS-E#bP zUMI+@&iPzN{QGdWgiP42{}jUKNdI}0eq%=bwd61HlG&pse$xfw{lq_O;x`kYSw5W} z*?$s$g#53}NPiRdKTWut^d;gT;W8;%niSs zvIs+*wt{9i&UT#5B3Wd}#miuvpr(}?+s))%i@KIu*hwaPq~z^@hFn+P`Z?7^;V zRDO?B?mq3$%IC?$B(%P&Q!eJlQ^q*mTt#d2Y2TMvmX?xo z`e;#U(;s0&##1OTQF5;Q7_E`#DfCD`NqNr_@ASvS!Txa5{%SDoBo*+VpY}I{X(tV) zm0Ts?-xDXk{Y2$`w0s^rk~20W&p(^EK7WvHATU|;okL)`x%PS#1=e-%FcJnmJV zpdE~|R^?fNK(#eWRp)0hGQ6tXYh{)DZT~jW%b!0xR|P7o@T}4 zs(kLY`FV_??L6hk%P(Rr6<%_(p0IMteWc3{7)+L^R;$myf@+mmRpBmEcnlO4=gLHJTuPthzz^W&tBS*2KY z^W8U??^;IXhhm_s>u$qte zC*WCXWmEE1bXU12C)i0zs@YqFnsIE|{tZ~mvlcT#1Ai{hk}%`iTQ$#1sr$6AT~?F= zPM58!(|ZxC);-KNsg+a5`_Y$nieQ&my+g!kC#S7sZ;7P>=1u5<1%37kYx;6<^B99kQ=H361+j?${!z9?M80^^)=QU1Swn*GoPn>Nu@ZqWXgC=`Z#WZKsTwgkMTw z`@ajHZ_V&uP5OLmtL0;D4C>p;c+Y&*;qk(Ycg+vA==Xm^-zlS%QmG%4pR<1jvFV;y zobsPFaUp+@8pw@ms2zSuoYM|Y8d=Z9-iyekUjk$M{69k?dAV`th?Dl%YL%(2o@s;m zx%=g-#LKgQP@qgD{j+F~d{2q@W$K-*ENAu4_ayx@k%j`(RH#T!FF(h&tm$Kj9V**j zO&R+9a_h_2>y&#&`a5wmk(M!1CR=#=D@#=!V`Zvn`pWR&CHzhBCA6enxkmYYWu8J+ z;{7$Fc82Ugm6xc1FW*PG9 z!kKvi@4}+Vs)6eF`P-x)ebqkMJvPGUWi8Gc4IdKT7r3}8#N6{2s}j3sl5c z;=7;n7vU|>_m=tk=&@z=R!NqeMe3{&?Pv#=TQi^~Su*B_$vh=gL1Zg*PW8+Vat!*} z`-O^6UrxZA7#U}jC%Yv3GxT#ng+2M%Wq7C({%578)?G-);<3-qw*BKMZ2|v0w1>>$ zD%!)@H$se>XXuHcwb?y4E=I?hcb5##12=A&i3?er!~Nr!x6V2vebX?QZsuS*FS$y- zO~lE33k7DX5HqFEUrdrb@l9KO!SU3zfDt0voDtELJuzgJGg>BC(^xU(9O;%}<6u7J zgZaojm9!niRYLpl??_MD!Ru+gJWq%@>*iU_DqHOtMOkLax)q#6?&T9CBpM#w=YAdA z=bvc8xsEpFk+P2Ux@CzDmSxspS=_iigK?J*#<_DNVamCanOMYLw7_4aLX1exZq!tE zm;uT!_a0pjPK@uNuN;kOTBF2TX4yfWLZBs2LqhW|rTiuINIpBKUHM*pW_8>2+QG71 zM!y%UAnlz+i^)8_nYfkEfZq03F(P8#8zddig_16A! zpQe}C!D@7jE$hay#U54ny=lg#_r|L4O@DxkLg=U1*jTAbR~B1!EAU*hjxV50CLMZD zdcQZl*jHD7zt3kqvC^c^x^d%^S>L<(iAKBfak#D)2|imv)2ib$5TcKIg@B_B>XV;?d96g`P(vS8I$CjpDr9>vMi9@j1%1{nU!{ZGWWRvEAK$ zN!>VaclSPQ-*xA|FLx~;sdp^v_)!vHocB7h@n=vi66ocZ(<#+%%S#_@JuuG$hDuirx8b!g^YnWuqOJW+WA-(y*@ zWL=ie^Q4jWOZHLM&Wp(OdBvZoe2p>Z%nc`xbHvr5FLOigo`s@l3g&yA z)nyv1%S;uloWjX|y2`1X%1PVyvthDQRC9teJom+VoqFr^N)-a*)UCJP`c^0Vw9V^2 zUMo)!)5jr@>jNJN?^_ebK>N$>;vfGE!mFk08miL3%9`8feL-QW;JT%{KTcJYZLkYS6*0G~IXE$eW z@zy!-?OkY{sA5%+XQ0lEaqi82$}Ia^KIwXOnf(@T3sCmCBE_@sXD%@_EsOq=8`D_g z^JLBz+5SS|eK$5dIZj4gV4jJUmB6d&9>}q1N3Db8fqcswda;T!*U-fPCh`2I_kcVra}OA8$sUl4 zOwJz89EK^v&VGlJ%*jw-zl7T;rv2sxsx{s^1M$}+QG2H%{%MySmm#hl(gIQo>Sz- z-{q^CUo1jZB(n)WmRK#)EB|)pZ=Pl4T9X18qP=S@?2fyh*)|VqbjSa{ii4spS0S_!Z|`vbLA$nz-k$dDKCl zzoyCg%g>l7MH6#8I4<~FJgCCM9bnCCe3_HLS@z#()|bH!VbQLR%p89 zc8K~37vj`56taUA)N7}g6@q)&0`E+6=^*usfspYOChdH8fJkQ#+@BZU26BWgf;gt){f)Ufmx4 z1d?iRk=i(YF_e2CyYFJ%3uQd-Tq4KR5s9>YTsZ_?6eodUK6FC-hT0@eJ=1 zDtVhc=W6QgeD~^oMCLy?C~M2SBE1&KZEZ{CINI?5mZY8Vt7C=dh;`2Mz0gtdcjswK zN&i7jZGS6N#{M+cu6*V~8L5QM(X?$WE0iZtR5g#g{#bT@D=?#)Ua6|n-mfeq#Z|3t zTv1l8D#{+E0*eBxtF19=`VIS3v)A%8dn|8rw&iQi!p}ciOFc-lk!B&~ND}(c0`*Yh zbdhSH0OD1`kAjyI4+LH% zJS{nkb7Y)^gwGDltlGyOJI?VbRr~7I#)H-PM`f3ByeiTkHMEk9rA4xnL2Dfvth3}+ zTtYp>n<6}CTiSyAw11(dC7eX3w-2bv*;#5`wwL?z<(6O8j2jlH>Fo#9I?F37cjZc? z<(@HW+fCKFj+ar-#nRT69hLgZ_+LzYW&DivpgHxuctm~WyzSO^oGQxk1nuA^-f@=t zk5=PyWcC_~_5qtVcy8mtT2Mq|5`>(alycj|=6!4yzq`21!+@k~UqYEVJ0SVQrFY35;zbS8yZ7nT56#MY< z*LWL564&xppr|w-J9gmYkt4P02@Zvl^mOdzrRqD{*uObOCSh-qi;2>4d!N_(RMRIGdRdWdjRKe1LwH^00H_t&bo-~Sr zbuc&zRISvAXVbb>7RvWqC*`fZ0(n)s;ABU~Kyx?mxfPsjJ~U9iLf&^PC~ewvl1?p< zmrx4^9*a%%1!1TQ_H^dPah$2L=xDx41V8Uw!LJnYwhoou)TWi&gTCI7YWApOT(qpBiu`jlqRL_f0 zUbHKymD*h5jKULyV}ES!=%`iqOA%vJ&sar%c^{p8);1qnyLIi!j$<81P7ZvwV_<7+ zXGh2B=GtX@JY!DquB6zi5e(IOFQ<=Io8pb)_E}Aw+ozud#hYlU{Ts@_sdFmW$9c%75z=&s2Q$a)J{2<|KLl+x5 z-%xqxDe-bP5qi0ya(|a_nW4f9g-WP$V6|N(aR?!@$!zb$QyxlSUwgdEQlB?Cn3@8;r(r)qfJ?Qj6A`6VIr50UO*gH z?{|jf%$XTi!hYj?Bp7=A?eJxfRiF4#qE~gCn`t5KD@6ma>w^zA)N*TuL8-#P; zlJpaR(20glf{IP?amo`4X-|jm*JTvlsX%N6d0$&-8C2vK1EJ-H%K1&g;`#yG!;zmj`jxAaiPJkXliddo|v1L*Tex0 zK`NR~$Lw9A6~<^HYA?x~=h$tC@3(v6;jU;`Fq}xFg5~jKu*puP!#zDlG%IhuqpG~+ zvo@)TbDK>O+IPp3=}3P%)sl?&CerGnC2l@Tx|88uy-_>8Bb?lo+CiDsAZU zD#30_9!sa9J>3+S2$i)W+_fvJiW;2eXh}v>6e*mJ#cegNaUdN{+TostSSryIJ}`Gr zcwboY4BoSYatzE0ECgo{D(A1>7P5KLAR@3_S#*oTuip1>|{pmEOtTp_^NaL0j z8|vE{;7sGFa+eE+Bavt-73+-k#L@=_`watjG?I>XscEieeWqf&)4xu^3S1SF=VUiL zq7O_p)+*21CY8IksXf%VzOjwaTIF4fh_AKE$CqY1i0I&VPZ)F3z!k#^YQ|T^g$h-p z7X7~`YO=fIHER4&!bAe&>DWG6rAAE~F7JsZ(+#n3Pkh&m75%ZEu4uAGO?o?(8Z~ZM zl#{SVl?;<+_J0k(_~O=@jz}{uDpoRx<2Ky-aBnoTKtl>zu`Qa4_v{;9pkXQl=^8cf zJo8SWnlO><4o9LhHrOkBVv)UTV<~bMYeOyIS{sq1Mom1=s@Sv1@6z*P+INSO(Jm_8 z9Zg1UM&OLbu2?!wM$_M2w?@r4Pbtw`QJ+XqW<7(P-aOwFS}ozo-teyIy0Fdn1@mJj z(%g1;Y*&9$Tf0zyh?2B*f0Dvy_WrJ9II%kx!9>{J7ba@*U|9#FTEa0qJ!54!QKP23 zUF@28%&t+TZx`L3KG4HB7;5w3#%omB1yZsiMw1Qw-Q6?R#k($?x{X=Cszz0wpSmj< z-XHGliB{Big%eDSsH`##Mp&a}y+ewX@t$~cNOh?91`9oCd`N5Yph3n;*M7!JMVGT2 zR+wec^x+H>92%@cWqFsR+{w<*C1xl3(-lqf?dG5Z4N=+BhH|<1pGlfI&*P~Iy8Bb%sgjj9-46A`jX%9?XvGo2CN&$%R;P7Mu@ITuX8q)*cUU8Yh)>O+f_ zE}5}EVimsZf;O*^B{~*y+VI^hhT{E^-9t)4186YY^BPmiaz!%UpJ3u1i1(*k!pU$i zgQR$PX`E)@LHLElPV3AF9Y`?vE_!>7^D@tDTvp|DGS1nLLB8NZHiqWwzHm>h%Pb6y z1Cb~zmJFYvl|fh6G47@sUb~@n`+U{&hQqMK$?#K&8Ps!!msc+`z0vgUcvr=Wa4M>2 z4fT^#moDju0iCJuY?aO)H?+0tQf8{1XL7`dJWrRyD#bCak-2N{k8xB@SFDZM(Qwjf zr4d76XgRrnZsx2t(V=)a#le761?Q|pyeFLO#B{}obMK6K@2)cc-BqgIU1dRyTKFzy zxxhhP%J=`1j52eseaD(#AUAysd8b4*YW4`L?m_&*$7CkMFmtZ8nV_RyHnEv){JmY(6}rb`Enk2e(NXnvQJn z)|y$a_5)n@dgT-}R0J+)QiKY(q5W&)5!@1v&!T?byaU|h%>Yt4>*ML5jUOnhrLA#Q zyYey;RE`-AD!Xk%s6N!Fvf4ImT;HI^v`L5K{D~x^QCsD-H#F~PTF-N*_O_KU&`{fk z=0@cW)nB1jiN~bN$ut;C;ds)Hqp0zc1kJr*Zg68NN(>r~*5&Pl<;Y&y31mTx8eVaO()+mcW2l^f8 zk%y%35LZcuB;czKJ!VLMsjI@_$$QNbs)fxO^cvHAlJ>gv` zRqPbVxV+q(k;)AlcMsZKUG9|uaaWgca*{S_FM7mTqOJ~&XQd!WR z11e8$FS|H`i>OmO4!xb>q{{A$e?}^2X+`|UP|Rf<#H%@gp|tfjz+tr+7eWwy18Sm zibwiWDyv)fVj(1rdnpumMyATa#VXD*l?Q{npo`k5QJh=FjmP9J9AwvpQ+sLSfCz^B zjMNyBrCB#S`_obg2@b?ko5DT)7$&qL9;c~oRUo`}v9`-Prg91ExupEUWx9`aMsiZ2 zZmxSy+tRXuw9cg=8i^d#%W?CJM?b@Bz6TQb52+GAInr23kz zim>9`^Q30ZMNJoxo>+;)Nq2#JK+)YQ&ApDX>Ub`6F`S)7cf`859gpnBeIHdhC_0@B zV>U}&$KmcgT{e5YYqj>9fw@EiS;Y2h{7xmjt(+|4wbPMTyNydvbS9>IE>ynR? z0MmV#&r+Ayj6UfJ<1f%)n+qLr(jZ}M74hc*tzwr9WPop!_ zF`U{IOQ!q7Jr2v023aTex(_1!F2}43M|NYQ(3P~X0CIj&`L3W{P6}fNLp-RlRlp7I z;K7aaz(-AD<-$$TWSi-*vOi5wT%O9;6YWl`TzRIX`(~7IYr~z<9yMCCvCd>Tc|hg& z$T`RC{iNcIBMQ6XO)2ib(WJ`l4eyPHIDsf{Z;Vp)#`{yz!8&C3_Ul!_XUl>#nnR=< z;_PNtB!5OgPe3)sm8=sRs?ZG@7m=Fe2-v7h+cLY%!=aeJ6IOajdyWFIKo!O|jF*n!Ith4M3`f#uE6YO_9mXM_@rMwBw&shn1R-neP8?)rI7$v#Y zC|@F;V)06_t_>#6W6Ha`Q!YHdlw??qf-_6hgk*G=JkLp*)^(kL>LNEHqg4hY^OJJZ zNu{05x7dVCjm-VhVDS;MA0{B^MK1F?bqk;FKX3A2mwEa@YH#d<@YfSRtom}_!+Q!gfYL|l;!%OgVNhi7c) zr{DNbSec|mj;~A=4ziBxR7uKpJbji^n&x=~OTVkJ$k#`A>E*&aOf5z94t{1eS;vo< zkAuAzaAGr`<*5=a8#YZw=nwM|>-N}?F-oNwP5*cw%n`fYd8jhu~BhN3PsAG7-tdNVF?s5|D=XeaEf zN%`#AGY0Q7qa?&y(am-b%SqN6P}Tu{JMjAJ8>+7!?k0-VB|U|AE620tuO&o1W`!B_a(Ez7z?!pG~6 zS^O2Dgg+wTm+OyP)|>T`Wa81_og#W+#a)*5=n4@{IvV__i0)o_H(D!rYb)y>3E$gj z9S)vand5nF<-L~mla)T-sm3hpsa2MB`xTaT{}oxh3RO~K%-APE?`WHqcetIxf|ouB)TB6@tw%QU(cU3xV58xg&* zPrO+quXMl9<2$lT3|0l+FOsLaKIHKox z;2w|lR1Yl?%kdqyE$d*PW!(!N?ekbK^yOGT*+-{&{;Dh3`9I8tPRlwQwk90+On7-w zvt{}CFJInQ`&2i-6A$D)x06tjebdN{W#K?Z*&gBKhB4#dXGP)YZPjQH0x_`hfH z-d#@KqCXlw09<}b249}RS7q?a;3dzd4B0mLoT*{KnFkB=EJW5#fdr;g%8M zZ6m^6Bf|1Gz4CF(DfIh%x$mT2Ls@+4%3s>b|DR0W%WObj{vT!8 z@bdnr{C~== 28) { + attributes.layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + } + if (Build.VERSION.SDK_INT >= 30) { + attributes.setFitInsetsTypes(0); + } window.setAttributes(attributes); } private void style(View view, String label) { view.setContentDescription(label); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(dp(44), dp(44)); - params.setMargins(dp(4), 0, dp(4), 0); - view.setLayoutParams(params); + view.setLayoutParams(new LinearLayout.LayoutParams(dp(44), dp(44))); } private int dp(int value) { @@ -354,6 +367,8 @@ private int dp(int value) { */ private static final class Pad extends Dialog { static final int INSET = 12; + /** Between the border and the logo or buttons in its corners. */ + static final int GAP = 12; Pad(Activity activity) { // Not a floating dialog, which would keep margins around it. @@ -440,6 +455,7 @@ private static final class LogoView extends View { private final Drawable logo; /** `logo` is an adaptive icon layer, with the art in its middle two thirds. */ private final boolean logoIsLayer; + private Bitmap art; LogoView(Context context) { super(context); @@ -460,11 +476,36 @@ private static final class LogoView extends View { @Override protected void onDraw(Canvas canvas) { int size = Math.min(getWidth(), getHeight()); - int left = (getWidth() - size) / 2; - int top = (getHeight() - size) / 2; + if (size <= 0) { + return; + } + if (art == null || art.getWidth() > size || art.getHeight() > size) { + art = render(size); + } + // The art itself in the corner, so its gaps to the border match the buttons'. + canvas.drawBitmap(art, 0, 0, null); + } + + /** The logo at `size`, trimmed to what it actually draws. */ + private Bitmap render(int size) { + Bitmap full = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); int bleed = logoIsLayer ? size / 4 : 0; - logo.setBounds(left - bleed, top - bleed, left + size + bleed, top + size + bleed); - logo.draw(canvas); + logo.setBounds(-bleed, -bleed, size + bleed, size + bleed); + logo.draw(new Canvas(full)); + int left = size; + int top = size; + for (int y = 0; y < size; y++) { + for (int x = 0; x < size; x++) { + if (Color.alpha(full.getPixel(x, y)) > 0) { + left = Math.min(left, x); + top = Math.min(top, y); + } + } + } + if (left == size) { + return full; + } + return Bitmap.createBitmap(full, left, top, size - left, size - top); } } From 0a4615ffde9f4928bebe7fc257cb2651afd77282 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 31 Oct 2026 17:46:21 +0000 Subject: [PATCH 7/7] chore: set version to v1.2.0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2653583..8e0968c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2348,7 +2348,7 @@ dependencies = [ [[package]] name = "localdesktop" -version = "1.1.1" +version = "1.2.0" dependencies = [ "android-sdkmanager-rs", "android_logger", diff --git a/Cargo.toml b/Cargo.toml index 30ecfe9..cd99f17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "localdesktop" -version = "1.1.1" +version = "1.2.0" edition = "2021" build = "build.rs" default-run = "build_apk"