fix: Added keyboard focus shortcut
This commit is contained in:
parent
ccc5a05ed7
commit
b4e9112989
7 changed files with 141 additions and 15 deletions
|
|
@ -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);
|
||||||
}
|
}
|
||||||
22
src/bun/api/controls/keyboard.ts
Normal file
22
src/bun/api/controls/keyboard.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 = "/";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue