Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
22 changes: 19 additions & 3 deletions src/routes/trackpad.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,28 @@ function TrackpadPage() {
setTimeout(() => send({ type: "click", button, press: false }), 50)
}

const handleCopy = () => {
send({ type: "copy" })
const handleCopy = async () => {
try {
let text = ""

if (navigator.clipboard && window.isSecureContext) {
text = await navigator.clipboard.readText()
} else {
// fallback → use selected text
text = window.getSelection()?.toString() || ""
}

send({
type: "clipboard-push",
text,
})
} catch (err) {
console.error("Copy failed", err)
}
}

const handlePaste = async () => {
send({ type: "paste" })
send({ type: "clipboard-pull" })
}

const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
Expand Down
42 changes: 41 additions & 1 deletion src/server/InputHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@ import { KEY_MAP } from "./KeyMap"
import { moveRelative } from "./ydotool"
import os from "node:os"

type ServerToClientMessage = {
type: "clipboard-text"
text: string
}

export interface InputMessage {
type:
| "move"
| "paste"
| "copy"
| "clipboard-push"
| "clipboard-pull"
| "click"
| "scroll"
| "key"
Expand All @@ -34,7 +41,10 @@ export class InputHandler {
private throttleMs: number
private modifier: Key

constructor(throttleMs = 8) {
constructor(
private sendToClient: (msg: ServerToClientMessage) => void,
throttleMs = 8,
) {
mouse.config.mouseSpeed = 1000
this.modifier = os.platform() === "darwin" ? Key.LeftSuper : Key.LeftControl
this.throttleMs = throttleMs
Expand Down Expand Up @@ -196,6 +206,36 @@ export class InputHandler {
break
}

case "clipboard-push": {
if (msg.text) {
// TEMP: fallback using typing instead of real clipboard
await keyboard.type(msg.text)
}
break
Comment on lines +209 to +218
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

clipboard-push is not a one-operation paste path.

keyboard.type(msg.text) replays keystrokes character-by-character, which does not satisfy the one-shot paste requirement and can alter behavior (shortcuts/IME/focus-sensitive fields).

🧰 Tools
🪛 GitHub Actions: CI

[error] 210-216: Biome formatting detected mismatch: File content differs from formatting output. Run the formatter and re-run the CI checks.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/InputHandler.ts` around lines 209 - 218, The current
"clipboard-push" case uses keyboard.type(msg.text) which types characters
one-by-one; replace it with a real one-shot paste: write msg.text to the
system/renderer clipboard (e.g., navigator.clipboard.writeText or a Node
clipboard helper like clipboardy) and then perform a single paste action (one
keyboard.press of the platform modifier + "V" or use the renderer's paste API)
instead of keyboard.type; update the "clipboard-push" handler to call clipboard
write with msg.text and then trigger a single paste keystroke (taking into
account Ctrl vs Meta for macOS) so paste is atomic and IME/shortcut-safe.

}

case "clipboard-pull": {
// simulate Ctrl+C to get current clipboard
try {
await keyboard.pressKey(this.modifier, Key.C)
} finally {
await Promise.allSettled([
keyboard.releaseKey(Key.C),
keyboard.releaseKey(this.modifier),
])
}

// small delay to allow clipboard update
await new Promise((r) => setTimeout(r, 100))

// ❗ send back to client (IMPORTANT)
this.sendToClient({
type: "clipboard-text",
text: "TEMP_CLIPBOARD_DATA", // ⚠️ temporary (we’ll improve later)
})
break
}

case "scroll": {
const MAX_SCROLL = 100
const promises: Promise<unknown>[] = []
Expand Down
Loading