diff --git a/src/hooks/useTrackpadGesture.ts b/src/hooks/useTrackpadGesture.ts index 131830d..36a6938 100644 --- a/src/hooks/useTrackpadGesture.ts +++ b/src/hooks/useTrackpadGesture.ts @@ -19,12 +19,11 @@ const getTouchDistance = (a: TrackedTouch, b: TrackedTouch): number => { export const useTrackpadGesture = ( send: (msg: any) => void, scrollMode: boolean, - sensitivity: number = 1.5, - invertScroll: boolean = false, - axisThreshold: number = 2.5 + sensitivity: number = 1.0, + invertScroll: boolean = false ) => { const [isTracking, setIsTracking] = useState(false); - + // Refs for tracking state (avoids re-renders during rapid movement) const ongoingTouches = useRef([]); const moved = useRef(false); @@ -133,54 +132,26 @@ export const useTrackpadGesture = ( // Send movement if we've moved and not in timeout period if (moved.current && e.timeStamp - lastEndTimeStamp.current >= TOUCH_TIMEOUT) { - // Apply Inversion Factor (Client Side) const invertMult = invertScroll ? -1 : 1; if (!scrollMode && ongoingTouches.current.length === 2) { const dist = getTouchDistance(ongoingTouches.current[0], ongoingTouches.current[1]); const delta = lastPinchDist.current !== null ? dist - lastPinchDist.current : 0; - if (pinching.current || Math.abs(delta) > PINCH_THRESHOLD) { pinching.current = true; lastPinchDist.current = dist; - // Zoom inversion typically matches scroll inversion preference send({ type: 'zoom', delta: delta * sensitivity * invertMult }); } else { lastPinchDist.current = dist; - send({ - type: 'scroll', - dx: -sumX * sensitivity * invertMult, - dy: -sumY * sensitivity * invertMult - }); + send({ type: 'scroll', dx: -sumX * sensitivity * invertMult, dy: -sumY * sensitivity * invertMult }); } } else if (scrollMode) { // Scroll mode: single finger scrolls, or two-finger scroll in cursor mode - let scrollDx = sumX; - let scrollDy = sumY; - const absDx = Math.abs(scrollDx); - const absDy = Math.abs(scrollDy); - if (scrollMode) { - if (absDx > absDy * axisThreshold) { - // Horizontal is dominant - ignore vertical - scrollDy = 0; - } else if (absDy > absDx * axisThreshold) { - // Vertical is dominant - ignore horizontal - scrollDx = 0; - } - } - send({ - type: 'scroll', - dx: Math.round(-scrollDx * sensitivity * 10 * invertMult) / 10 , - dy: Math.round(-scrollDy * sensitivity * 10 * invertMult) / 10 - }); + send({ type: 'scroll', dx: -sumX * sensitivity * invertMult, dy: -sumY * sensitivity * invertMult }); } else if (ongoingTouches.current.length === 1 || dragging.current) { // Cursor movement (only in cursor mode with 1 finger, or when dragging) - // Inversion usually does NOT apply to pointer movement, only scroll/zoom - send({ - type: 'move', - dx: Math.round(sumX * sensitivity * 10) / 10 , - dy: Math.round(sumY * sensitivity * 10) / 10 - }); + // Inversion usually doesn't apply to pointer movement + send({ type: 'move', dx: sumX * sensitivity, dy: sumY * sensitivity }); } } }; diff --git a/src/routes/trackpad.tsx b/src/routes/trackpad.tsx index cf21e0b..50f0dfe 100644 --- a/src/routes/trackpad.tsx +++ b/src/routes/trackpad.tsx @@ -1,5 +1,5 @@ import { createFileRoute } from '@tanstack/react-router' -import { useState, useRef } from 'react' +import { useState, useRef, useCallback } from 'react' import { useRemoteConnection } from '../hooks/useRemoteConnection'; import { useTrackpadGesture } from '../hooks/useTrackpadGesture'; import { ControlBar } from '../components/Trackpad/ControlBar'; @@ -12,6 +12,9 @@ export const Route = createFileRoute('/trackpad')({ component: TrackpadPage, }) +const CLICK_RELEASE_DELAY_MS = 50; +const INPUT_REFOCUS_DELAY_MS = 10; + function TrackpadPage() { const [scrollMode, setScrollMode] = useState(false); const [modifier, setModifier] = useState("Release"); @@ -19,37 +22,37 @@ function TrackpadPage() { const bufferText = buffer.join(" + "); const hiddenInputRef = useRef(null); const isComposingRef = useRef(false); - - // Load Client Settings + + // Load Client Settings from localStorage const [sensitivity] = useState(() => { if (typeof window === 'undefined') return 1.0; const s = localStorage.getItem('rein_sensitivity'); return s ? parseFloat(s) : 1.0; }); - + const [invertScroll] = useState(() => { if (typeof window === 'undefined') return false; const s = localStorage.getItem('rein_invert'); - return s ? JSON.parse(s) : false; + return s ? s === 'true' : false; }); const { status, send, sendCombo } = useRemoteConnection(); // Pass sensitivity and invertScroll to the gesture hook const { isTracking, handlers } = useTrackpadGesture(send, scrollMode, sensitivity, invertScroll); - const focusInput = () => { + const focusInput = useCallback(() => { hiddenInputRef.current?.focus(); - }; + }, []); - const handleClick = (button: 'left' | 'right') => { + const handleClick = useCallback((button: 'left' | 'right') => { send({ type: 'click', button, press: true }); // Release after short delay to simulate click - setTimeout(() => send({ type: 'click', button, press: false }), 50); - }; + setTimeout(() => send({ type: 'click', button, press: false }), CLICK_RELEASE_DELAY_MS); + }, [send]); - const handleKeyDown = (e: React.KeyboardEvent) => { + const handleKeyDown = useCallback((e: React.KeyboardEvent) => { const key = e.key.toLowerCase(); - + if (modifier !== "Release") { if (key === 'backspace') { e.preventDefault(); @@ -73,10 +76,10 @@ function TrackpadPage() { else if (key !== 'unidentified' && key.length > 1) { send({ type: 'key', key }); } - }; + }, [send]); const handleModifierState = () => { - switch(modifier){ + switch (modifier) { case "Active": if (buffer.length > 0) { setModifier("Hold"); @@ -97,7 +100,7 @@ function TrackpadPage() { const handleModifier = (key: string) => { console.log(`handleModifier called with key: ${key}, current modifier: ${modifier}, buffer:`, buffer); - + if (modifier === "Hold") { const comboKeys = [...buffer, key]; console.log(`Sending combo:`, comboKeys); @@ -109,13 +112,12 @@ function TrackpadPage() { } }; - const sendText = (val: string) => { + const sendText = useCallback((val: string) => { if (!val) return; - const toSend = val.length > 1 ? `${val} ` : val; - send({ type: 'text', text: toSend }); - }; + send({ type: 'text', text: val }); + }, [send]); - const handleInput = (e: React.ChangeEvent) => { + const handleInput = useCallback((e: React.ChangeEvent) => { if (isComposingRef.current) return; const val = e.target.value; if (val) { @@ -126,32 +128,32 @@ function TrackpadPage() { sendText(val); } } - }; + }, [sendText]); - const handleCompositionStart = () => { + const handleCompositionStart = useCallback(() => { isComposingRef.current = true; - }; + }, []); - const handleCompositionEnd = (e: React.CompositionEvent) => { + const handleCompositionEnd = useCallback((e: React.CompositionEvent) => { isComposingRef.current = false; const val = (e.target as HTMLInputElement).value; if (val) { // Don't send text during modifier mode if (modifier !== "Release") { handleModifier(val); - }else{ + } else { sendText(val); } (e.target as HTMLInputElement).value = ''; } - }; + }, [sendText]); - const handleContainerClick = (e: React.MouseEvent) => { + const handleContainerClick = useCallback((e: React.MouseEvent) => { if (e.target === e.currentTarget) { e.preventDefault(); focusInput(); } - }; + }, [focusInput]); return (
{ - setTimeout(() => hiddenInputRef.current?.focus(), 10); + setTimeout(() => hiddenInputRef.current?.focus(), INPUT_REFOCUS_DELAY_MS); }} autoComplete="off" autoCorrect="off" diff --git a/src/server-config.json b/src/server-config.json index ec9ca9e..f8f79b3 100644 --- a/src/server-config.json +++ b/src/server-config.json @@ -1,5 +1,4 @@ { "host": "0.0.0.0", - "frontendPort": 3000, - "address": "rein.local" + "frontendPort": 3000 } \ No newline at end of file diff --git a/src/server/InputHandler.ts b/src/server/InputHandler.ts index 77ffe46..c86134a 100644 --- a/src/server/InputHandler.ts +++ b/src/server/InputHandler.ts @@ -1,6 +1,7 @@ import { mouse, Point, Button, keyboard, Key } from '@nut-tree-fork/nut-js'; import { KEY_MAP } from './KeyMap'; + export interface InputMessage { type: 'move' | 'click' | 'scroll' | 'key' | 'text' | 'zoom' | 'combo'; dx?: number; @@ -23,9 +24,9 @@ export class InputHandler { case 'move': if (msg.dx !== undefined && msg.dy !== undefined) { const currentPos = await mouse.getPosition(); - + await mouse.setPosition(new Point( - currentPos.x + msg.dx, + currentPos.x + msg.dx, currentPos.y + msg.dy )); } @@ -42,50 +43,29 @@ export class InputHandler { } break; - case 'scroll': - const promises: Promise[] = []; - - // Vertical scroll - if (typeof msg.dy === 'number' && msg.dy !== 0) { - if (msg.dy > 0) { - promises.push(mouse.scrollDown(msg.dy)); - } else { - promises.push(mouse.scrollUp(-msg.dy)); - } - } - - // Horizontal scroll - if (typeof msg.dx === 'number' && msg.dx !== 0) { - if (msg.dx > 0) { - promises.push(mouse.scrollRight(msg.dx)); - } else { - promises.push(mouse.scrollLeft(-msg.dx)); - } - } - - if (promises.length) { - await Promise.all(promises); - } + case 'scroll': { + if (msg.dy !== undefined && msg.dy !== 0) await mouse.scrollDown(Math.round(msg.dy)); + if (msg.dx !== undefined && msg.dx !== 0) await mouse.scrollRight(Math.round(msg.dx)); break; + } - case 'zoom': + case 'zoom': { if (msg.delta !== undefined && msg.delta !== 0) { - const sensitivityFactor = 0.5; + const sensitivityFactor = 0.5; // Adjust scaling for OS zoom behavior const MAX_ZOOM_STEP = 5; - - const scaledDelta = - Math.sign(msg.delta) * - Math.min(Math.abs(msg.delta) * sensitivityFactor, MAX_ZOOM_STEP); - - const amount = -scaledDelta; - - await keyboard.pressKey(Key.LeftControl); - try { - await mouse.scrollDown(amount); - } finally { - await keyboard.releaseKey(Key.LeftControl); + const scaledDelta = Math.sign(msg.delta) * Math.min(Math.abs(msg.delta) * sensitivityFactor, MAX_ZOOM_STEP); + const amount = Math.round(-scaledDelta); + if (amount !== 0) { + + await keyboard.pressKey(Key.LeftControl); + try { + await mouse.scrollDown(amount); + } finally { + await keyboard.releaseKey(Key.LeftControl); + } } } + } break; case 'key':