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
5 changes: 5 additions & 0 deletions browser/src/components/keyboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getOperatingSystem } from '@/libs/browser';
import { device } from '@/libs/device';
import { KeyboardReport } from '@/libs/keyboard/keyboard.ts';
import { isModifier } from '@/libs/keyboard/keymap.ts';
import { learnFromKeyEvent } from '@/libs/keyboard/layouts.ts';

interface AltGrState {
active: boolean;
Expand Down Expand Up @@ -69,6 +70,10 @@ export const Keyboard = () => {
}

pressedKeys.current.add(code);

// Learn character mappings for paste feature
learnFromKeyEvent(event);

await handleKeyEvent({ type: 'keydown', code });
}

Expand Down
80 changes: 57 additions & 23 deletions browser/src/components/menu/keyboard/paste.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,62 @@ import { ClipboardIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next';

import { device } from '@/libs/device';
import { CharCodes, ShiftChars } from '@/libs/keyboard/charCodes.ts';
import { getModifierBit } from '@/libs/keyboard/keymap.ts';
import { getLayoutById, initLayoutDetection, LayoutMap } from '@/libs/keyboard/layouts.ts';
import { ModifierBits } from '@/libs/keyboard/keymap.ts';

// Initialize layout detection early
initLayoutDetection();

// Paste text as keystrokes using the specified keyboard layout
export async function pasteText(text: string, layoutId: string = 'auto'): Promise<void> {
const layout: LayoutMap = getLayoutById(layoutId);

// Release all keys first to ensure clean state
await device.sendKeyboardData([0, 0, 0, 0, 0, 0, 0, 0]);
await new Promise((r) => setTimeout(r, 50));

for (const char of text) {
const mapping = layout[char];
if (!mapping) {
console.warn(`No mapping for character: '${char}' (code ${char.charCodeAt(0)})`);
continue;
}

let modifier = 0;
if (mapping.shift) {
modifier |= ModifierBits.LeftShift;
}
if (mapping.altGr) {
// AltGr is typically Right Alt
modifier |= ModifierBits.RightAlt;
}

// For modified keys (Shift/AltGr), press modifier first, then key
// This is more compatible with Windows login screen
if (modifier !== 0) {
await device.sendKeyboardData([modifier, 0, 0, 0, 0, 0, 0, 0]);
await new Promise((r) => setTimeout(r, 20));
}

// Press key (with modifier held)
await device.sendKeyboardData([modifier, 0, mapping.code, 0, 0, 0, 0, 0]);
await new Promise((r) => setTimeout(r, 50));

// Release key (modifier still held)
if (modifier !== 0) {
await device.sendKeyboardData([modifier, 0, 0, 0, 0, 0, 0, 0]);
await new Promise((r) => setTimeout(r, 15));
}

// Release modifier
await device.sendKeyboardData([0, 0, 0, 0, 0, 0, 0, 0]);
if (mapping.altGr) {
await new Promise((r) => setTimeout(r, 20));
await device.sendKeyboardData([0, 0, 0, 0, 0, 0, 0, 0]);
}
await new Promise((r) => setTimeout(r, 30));
}
}

export const Paste = () => {
const { t } = useTranslation();
Expand All @@ -17,34 +71,14 @@ export const Paste = () => {
try {
const text = await navigator.clipboard.readText();
if (!text) return;

for (const char of text) {
const ascii = char.charCodeAt(0);

const code = CharCodes[ascii];
if (!code) continue;

let modifier = 0;
if ((ascii >= 65 && ascii <= 90) || ShiftChars[ascii]) {
modifier |= getModifierBit('ShiftLeft');
}

await send(modifier, code);
await new Promise((r) => setTimeout(r, 50));
await send(0, 0);
}
await pasteText(text);
} catch (e) {
console.log(e);
} finally {
setIsLoading(false);
}
}

async function send(modifier: number, code: number): Promise<void> {
const keys = [modifier, 0, code, 0, 0, 0, 0, 0];
await device.sendKeyboardData(keys);
}

return (
<div
className="flex h-[32px] cursor-pointer items-center space-x-2 rounded px-3 text-neutral-300 hover:bg-neutral-700/50"
Expand Down
6 changes: 6 additions & 0 deletions browser/src/jotai/keyboard.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { atom } from 'jotai';

import * as storage from '@/libs/storage';

export const isKeyboardEnableAtom = atom(true);

export const isKeyboardOpenAtom = atom(false);

export const targetKeyboardLayoutAtom = atom(storage.getTargetKeyboardLayout());

export const pasteSpeedAtom = atom<number>(storage.getPasteSpeed());
Loading