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
32 changes: 21 additions & 11 deletions src/routes/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ export const Route = createFileRoute('/settings')({
component: SettingsPage,
})

/**
* SettingsPage component for configuring server IP, ports, and mouse sensitivity.
* Also handles real-time IP detection and QR code generation.
*
* @returns {JSX.Element} The rendered settings interface.
*/
function SettingsPage() {
const [ip, setIp] = useState('');
const [frontendPort, setFrontendPort] = useState(String(CONFIG.FRONTEND_PORT));
Expand Down Expand Up @@ -46,33 +52,37 @@ function SettingsPage() {
if (typeof window === 'undefined') return;
if (window.location.hostname !== 'localhost') return;

console.log('Attempting to auto-detect IP...');
console.log('Starting IP auto-detection stream...');
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws`;
const socket = new WebSocket(wsUrl);

socket.onopen = () => {
console.log('Connected to local server for IP detection');
console.log('Connected to local server for live IP tracking');
socket.send(JSON.stringify({ type: 'get-ip' }));
};

socket.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.type === 'server-ip' && data.ip) {
console.log('Auto-detected IP:', data.ip);
setIp(data.ip);
socket.close();
// Handle both initial response and subsequent broadcasts
if ((data.type === 'server-ip' || data.type === 'connected') && (data.ip || data.serverIp)) {
const newIp = data.ip || data.serverIp;
console.log('Received IP Update:', newIp);
setIp(newIp);
}
} catch (e) {
console.error(e);
console.error('WS Message Error:', e);
}
};

return () => {
if (socket.readyState === WebSocket.OPEN) socket.close();
if (socket.readyState !== WebSocket.CLOSING && socket.readyState !== WebSocket.CLOSED) {
console.log('Closing IP detection socket');
socket.close();
}
}
}, []); // Run once
}, []); // Run once on mount, stays open for updates

// Helper for display URL
const displayUrl = typeof window !== 'undefined'
Expand Down Expand Up @@ -100,12 +110,12 @@ function SettingsPage() {
</label>
</div>

{/* SENSITIVITY SLIDER SECTION */}
{/* SENSITIVITY SLIDER SECTION */}
<div className="form-control w-full max-w-2xl mx-auto">
<label className="label" htmlFor="sensitivity-slider">
<span className="label-text">Mouse Sensitivity</span>
<span className="label-text-alt font-mono">
{sensitivity.toFixed(1)}x
{sensitivity.toFixed(1)}x
</span>
</label>

Expand Down
12 changes: 9 additions & 3 deletions src/server/InputHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,22 @@ export class InputHandler {
mouse.config.mouseSpeed = 1000;
}

/**
* Processes incoming input messages and performs corresponding system actions.
* Supports mouse movement, clicking, scrolling, zooming, keyboard input, and workspace switching.
*
* @param {InputMessage} msg - The input message containing the action type and parameters.
*/
async handleMessage(msg: InputMessage) {
switch (msg.type) {
case 'move':
if (msg.dx !== undefined && msg.dy !== undefined) {
const currentPos = await mouse.getPosition();
// Apply sensitivity multiplier
const sensitivity = CONFIG.MOUSE_SENSITIVITY ?? 1.0;

await mouse.setPosition(new Point(
currentPos.x + (msg.dx * sensitivity),
currentPos.x + (msg.dx * sensitivity),
currentPos.y + (msg.dy * sensitivity)
));
}
Expand Down Expand Up @@ -57,7 +63,7 @@ export class InputHandler {
const MAX_ZOOM_STEP = 5;
const scaledDelta = Math.sign(msg.delta) * Math.min(Math.abs(msg.delta) * sensitivityFactor, MAX_ZOOM_STEP);
const amount = -scaledDelta * invertMultiplier;

await keyboard.pressKey(Key.LeftControl);
try {
await mouse.scrollDown(amount);
Expand Down
53 changes: 48 additions & 5 deletions src/server/websocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import fs from 'fs';
import { Server, IncomingMessage } from 'http';
import { Socket } from 'net';

// Helper to find LAN IP
/**
* Retrieves the local IPv4 address of the host machine.
* Filters for non-internal (non-loopback) IPv4 interfaces.
*
* @returns {string} The local IPv4 address or 'localhost' if no interface is found.
*/
function getLocalIp() {
const nets = os.networkInterfaces();
for (const name of Object.keys(nets)) {
Expand All @@ -18,13 +23,49 @@ function getLocalIp() {
return 'localhost';
}

/**
* Initializes the WebSocket server and sets up network interface polling.
* Handles WebSocket upgrades, client connections, and real-time IP broadcasts.
*
* @param {Server} server - The HTTP server instance to attach the WebSocket server to.
*/
export function createWsServer(server: Server) {
const wss = new WebSocketServer({ noServer: true });
const inputHandler = new InputHandler();
const LAN_IP = getLocalIp();

let currentIp = getLocalIp();

console.log(`WebSocket Server initialized (Upgrade mode)`);
console.log(`WS LAN IP: ${LAN_IP}`);
console.log(`Initial WS LAN IP: ${currentIp}`);

// Frequency for network interface polling (ms)
const POLLING_INTERVAL = 5000;

const pollingIntervalId = setInterval(() => {
const newIp = getLocalIp();
if (newIp !== currentIp) {
console.log(`Network Change Detected! IP: ${currentIp} -> ${newIp}`);
currentIp = newIp;

// Broadcast the new IP to all connected clients
const updateMsg = JSON.stringify({ type: 'server-ip', ip: currentIp });
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(updateMsg);
}
});
}
}, POLLING_INTERVAL);

// Cleanup interval when the WebSocket server closes
wss.on('close', () => {
console.log('Clearing network polling interval');
clearInterval(pollingIntervalId);
});

// Also handle process exit to ensure cleanup
process.on('SIGTERM', () => clearInterval(pollingIntervalId));
process.on('SIGINT', () => clearInterval(pollingIntervalId));

server.on('upgrade', (request: IncomingMessage, socket: Socket, head: Buffer) => {
const pathname = request.url;
Expand All @@ -39,17 +80,19 @@ export function createWsServer(server: Server) {
wss.on('connection', (ws: WebSocket) => {
console.log('Client connected to /ws');

ws.send(JSON.stringify({ type: 'connected', serverIp: LAN_IP }));
// Send current IP immediately on connection
ws.send(JSON.stringify({ type: 'connected', ip: currentIp }));

ws.on('message', async (data: string) => {
try {
const raw = data.toString();
const msg = JSON.parse(raw);

if (msg.type === 'get-ip') {
ws.send(JSON.stringify({ type: 'server-ip', ip: LAN_IP }));
ws.send(JSON.stringify({ type: 'server-ip', ip: currentIp }));
return;
}
// ... existing update-config logic ...

if (msg.type === 'update-config') {
console.log('Updating config:', msg.config);
Expand Down