fix: Added keyboard focus shortcut

This commit is contained in:
Simeon Radivoev 2026-03-30 20:51:48 +03:00
parent ccc5a05ed7
commit b4e9112989
Signed by: simeonradivoev
GPG key ID: 7611A451D2A5D37A
7 changed files with 141 additions and 15 deletions

View file

@ -5,9 +5,23 @@ import { GamepadManager } from './manager';
export default async function Initialize () export default async function Initialize ()
{ {
let startSelectPressed = false; let startSelectPressed = false;
let endPressed = false;
const manager = new GamepadManager(); const manager = new GamepadManager();
function handleFocus ()
{
const launchGameTask = taskQueue.findJob(LaunchGameJob.id, LaunchGameJob);
if (launchGameTask)
{
launchGameTask.abort('exit');
taskQueue.waitForJob(LaunchGameJob.id).then(() => setTimeout(() => events.emit('focus'), 300));
} else
{
events.emit('focus');
}
}
setInterval(() => setInterval(() =>
{ {
for (const pad of manager.getGamepads()) for (const pad of manager.getGamepads())
@ -20,21 +34,26 @@ export default async function Initialize ()
if (!startSelectPressed) if (!startSelectPressed)
{ {
startSelectPressed = true; startSelectPressed = true;
console.log("Focus"); handleFocus();
const launchGameTask = taskQueue.findJob(LaunchGameJob.id, LaunchGameJob);
if (launchGameTask)
{
launchGameTask.abort('exit');
taskQueue.waitForJob(LaunchGameJob.id).then(() => setTimeout(() => events.emit('focus'), 300));
} else
{
events.emit('focus');
}
} }
} else } else
{ {
startSelectPressed = false; startSelectPressed = false;
} }
} }
const keyboard = manager.getKeyboard();
const keyState = keyboard.update();
if (keyState?.keys.End && keyState?.keys.LeftControl)
{
if (!endPressed)
{
endPressed = true;
handleFocus();
}
} else
{
endPressed = false;
}
}, 100); }, 100);
} }

View file

@ -0,0 +1,22 @@
import type { IKeyboardBackend, KeyboardState } from "./types";
export class Keybaord
{
private backend: IKeyboardBackend | undefined;
async init ()
{
if (process.platform === "win32")
{
const { KeyboardWindows } = await import("./windows");
this.backend = new KeyboardWindows();
} else
{
}
}
update (): KeyboardState | null
{
return this.backend?.update() ?? null;
}
}

View file

@ -1,19 +1,23 @@
import { Gamepad } from "./gamepad"; import { Gamepad } from "./gamepad";
import { platform } from "os"; import { platform } from "os";
import { Keybaord } from "./keyboard";
export class GamepadManager export class GamepadManager
{ {
private gamepads: Gamepad[] = []; private gamepads: Gamepad[] = [];
private keyboard: Keybaord;
private scanInterval: any; private scanInterval: any;
constructor() constructor()
{ {
this.scanGamepads(); this.scanGamepads();
this.keyboard = new Keybaord();
this.keyboard.init();
// scan every second for new/disconnected devices // scan every second for new/disconnected devices
this.scanInterval = setInterval(() => this.scanGamepads(), 1000); this.scanInterval = setInterval(async () => this.scanGamepads(), 1000);
} }
private scanGamepads () private async scanGamepads ()
{ {
const max = platform() === "win32" ? 4 : 8; // max controllers const max = platform() === "win32" ? 4 : 8; // max controllers
for (let i = 0; i < max; i++) for (let i = 0; i < max; i++)
@ -23,6 +27,7 @@ export class GamepadManager
try try
{ {
const pad = new Gamepad(i); const pad = new Gamepad(i);
await pad.init();
if (pad.update()) if (pad.update())
{ {
this.gamepads[i] = pad; this.gamepads[i] = pad;
@ -42,6 +47,11 @@ export class GamepadManager
} }
} }
getKeyboard ()
{
return this.keyboard;
}
getGamepads () getGamepads ()
{ {
return this.gamepads.filter(Boolean); return this.gamepads.filter(Boolean);

View file

@ -5,6 +5,21 @@ export type ButtonName =
| "START" | "SELECT" | "START" | "SELECT"
| "L3" | "R3"; | "L3" | "R3";
export type KeyCode =
| "ArrowUp" | "ArrowDown" | "ArrowLeft" | "ArrowRight"
| "KeyW" | "KeyA" | "KeyS" | "KeyD"
| "Enter" | "Escape" | "Space" | "End" | "LeftShift" | "RightShift" | "LeftControl" | "RightControl" | "LeftAlt" | "RightAlt";
export interface KeyboardState
{
keys: Record<KeyCode, boolean>;
}
export interface IKeyboardBackend
{
update (): KeyboardState;
}
export interface Stick export interface Stick
{ {
x: number; // -1 → 1 x: number; // -1 → 1

View file

@ -1,11 +1,72 @@
import { IGamepadBackend, GamepadState, ButtonName } from "./types"; import { IGamepadBackend, GamepadState, ButtonName, IKeyboardBackend, KeyboardState, KeyCode } from "./types";
import { dlopen, FFIType } from "bun:ffi"; import { dlopen, FFIType } from "bun:ffi";
const xinput = dlopen("xinput1_4.dll", { const xinput = dlopen("xinput1_4.dll", {
XInputGetState: { args: [FFIType.u32, FFIType.ptr], returns: FFIType.u32 }, XInputGetState: { args: [FFIType.u32, FFIType.ptr], returns: FFIType.u32 },
}); });
const user32 = dlopen("user32.dll", {
GetAsyncKeyState: {
args: [FFIType.i32],
returns: FFIType.i16,
},
});
// Virtual key codes
const VK: Record<KeyCode, number> = {
ArrowUp: 0x26,
ArrowDown: 0x28,
ArrowLeft: 0x25,
ArrowRight: 0x27,
KeyW: 0x57,
KeyA: 0x41,
KeyS: 0x53,
KeyD: 0x44,
Enter: 0x0D,
Escape: 0x1B,
Space: 0x20,
End: 0x23,
LeftShift: 0xA0,
RightShift: 0xA1,
LeftControl: 0xA2,
RightControl: 0xA3,
LeftAlt: 0xA4,
RightAlt: 0xA5,
};
const ERROR_SUCCESS = 0; const ERROR_SUCCESS = 0;
export class KeyboardWindows implements IKeyboardBackend
{
private keys: Record<KeyCode, boolean> = {} as any;
update (): KeyboardState
{
const next: Record<KeyCode, boolean> = {} as any;
// default all keys to false
// poll keys globally
for (const vkStr in VK)
{
const vk = Number(VK[vkStr as KeyCode]);
const key = vkStr;
const state = user32.symbols.GetAsyncKeyState(vk);
if ((state & 0x8000) !== 0)
{
next[key as KeyCode] = true;
}
}
this.keys = next;
return { keys: this.keys };
}
}
export class GamepadWindows implements IGamepadBackend export class GamepadWindows implements IGamepadBackend
{ {
private index: number; private index: number;

View file

@ -70,7 +70,6 @@ export class LaunchGameJob implements IJob<z.infer<typeof LaunchGameJob.dataSche
// We have full control over launching integrated emulators better to use bun spawn // We have full control over launching integrated emulators better to use bun spawn
const bunGame = Bun.spawn([this.validCommand.metadata.emulatorBin, ...commandArgs], { const bunGame = Bun.spawn([this.validCommand.metadata.emulatorBin, ...commandArgs], {
cwd: this.validCommand.startDir, cwd: this.validCommand.startDir,
windowsVerbatimArguments: true,
signal: context.abortSignal signal: context.abortSignal
}); });

View file

@ -464,7 +464,7 @@ const assets = new Set<string>([
]); ]);
// Store basePath resolved from Vite config // Store basePath resolved from Vite config
const BASE_PATH = "./"; const BASE_PATH = "/";
/** /**