Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@ import android.widget.Toast
import com.vortex.a3.service.VortexService

/**
* Share-sheet target: the user picks "Vortex" when sharing one or more files
* (any type) to the laptop — instant-share style. The files arrive as granted
* `content://` URIs in the intent (no focus trick needed). Each is read and
* handed to [VortexService] as a FILE; the laptop saves them to Downloads (NOT
* the clipboard). Handles both single (`ACTION_SEND`) and multi
* (`ACTION_SEND_MULTIPLE`) shares — file managers use the latter for a
* multi-selection, which is why a SEND-only filter never appeared.
* Share-sheet target: the user picks "Vortex" when sharing to the laptop —
* instant-share style. Three kinds of share, in priority order:
*
* 1. text containing a URL → browsing handoff (the laptop opens the page),
* 2. any attachment → FILE to the laptop's download folder,
* 3. plain text → the laptop's CLIPBOARD.
*
* Files arrive as granted `content://` URIs in the intent (no focus trick
* needed) and are handed to [VortexService] as FILEs. Handles both single
* (`ACTION_SEND`) and multi (`ACTION_SEND_MULTIPLE`) shares — file managers use
* the latter for a multi-selection, which is why a SEND-only filter never
* appeared.
*/
class ShareReceiverActivity : Activity() {

Expand Down Expand Up @@ -49,6 +54,28 @@ class ShareReceiverActivity : Activity() {
else -> emptyList()
}

// Shared plain TEXT (no URL in it, so the handoff above passed) → the
// laptop's CLIPBOARD, via the same bus the Quick Settings tile uses, so
// it inherits the cap + chunking + per-peer send. Without this, a text
// share fell through to the file loop below with nothing to read and
// died on "Couldn't read the shared file(s)" — the manifest advertises
// text/plain, so Vortex offers itself for text and must honour it.
//
// Guarded on `uris.isEmpty()`: a share can carry a caption ALONGSIDE an
// attachment (EXTRA_TEXT + EXTRA_STREAM), and there the file is the
// payload the user meant.
if (uris.isEmpty() && intent?.action == Intent.ACTION_SEND) {
val text = intent.getStringExtra(Intent.EXTRA_TEXT)?.trim()
if (!text.isNullOrEmpty()) {
VortexService.clipboardBus.tryEmit(text)
Log.i(TAG, "share: forwarded ${text.length} chars to the laptop clipboard")
Toast.makeText(this, "Sending text to laptop…", Toast.LENGTH_SHORT).show()
finish()
overridePendingTransition(0, 0)
return
}
}

var sent = 0
for (uri in uris) {
val file = ClipboardFileReader.read(this, uri)
Expand Down
22 changes: 20 additions & 2 deletions linux/ui-tauri/src-tauri/src/clipboard_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,10 +259,28 @@ pub(crate) fn spawn_clipboard_sync(
*g = sync_sig(&text);
}
let t2 = text.clone();
let _ = tokio::task::spawn_blocking(move || set_system_text(t2)).await;
// Never swallow the apply error: "the phone's text didn't show
// up in my clipboard" is indistinguishable from "it never
// arrived" without this, and the two have nothing in common to
// debug. (The image path below has always logged its failures.)
let applied = match tokio::task::spawn_blocking(move || set_system_text(t2)).await {
Ok(Ok(())) => true,
Ok(Err(e)) => {
tracing::warn!("clipboard: phone text NOT applied: {e}");
false
}
Err(e) => {
tracing::warn!("clipboard: apply task join: {e}");
false
}
};
// Still kept in history even if the system clipboard refused it,
// so the text is at least reachable from the Super+V popup.
store_capture("text", Some(text), None);
let _ = app.emit("vortex:clipboard", ());
tracing::info!("clipboard: synced from phone");
if applied {
tracing::info!("clipboard: synced from phone");
}
}
});
}
Expand Down