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
69 changes: 65 additions & 4 deletions src/platform/Hotkeys.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,72 @@
import { type CommandSinkRef, HotkeyConfig, HotkeySystem, type Language } from "../livesplit-core";
import {
type CommandSinkRef,
HotkeyConfig,
HotkeySystem,
type Language,
} from "../livesplit-core";
import { expect } from "../util/OptionUtil";

export interface HotkeyImplementation {
ptr?: number;
config(lang: Language | undefined): Promise<HotkeyConfig> | HotkeyConfig;
setConfig(config: HotkeyConfig): void;
activate(): void;
deactivate(): void;
resolve(keyCode: string): Promise<string> | string;
addWindow(window: Window): void;
}

class LocalHotkeys implements HotkeyImplementation {
constructor(private hotkeySystem: HotkeySystem) {}

public config(_lang: Language | undefined): HotkeyConfig {
return this.hotkeySystem.config();
}

public setConfig(config: HotkeyConfig): void {
this.hotkeySystem.setConfig(config);
}

public activate(): void {
this.hotkeySystem.activate();
}

public deactivate(): void {
this.hotkeySystem.deactivate();
}

public resolve(keyCode: string): string {
return this.hotkeySystem.resolve(keyCode);
}

public addWindow(childWindow: Window): void {
childWindow.addEventListener("keydown", (event) => {
// Each browser window has its own JavaScript realm. Forwarding a
// newly-created event into the main window lets the existing
// hotkey listener process popup input without passing a child
// realm's Window or KeyboardEvent through the WASM boundary.
const forwardedEvent = new KeyboardEvent(event.type, {
key: event.key,
code: event.code,
location: event.location,
ctrlKey: event.ctrlKey,
shiftKey: event.shiftKey,
altKey: event.altKey,
metaKey: event.metaKey,
repeat: event.repeat,
isComposing: event.isComposing,
bubbles: true,
cancelable: true,
});

if (!window.dispatchEvent(forwardedEvent)) {
event.preventDefault();
}
});
}
}

class GlobalHotkeys implements HotkeyImplementation {
constructor(private hotkeySystem?: HotkeySystem) { }
constructor(private hotkeySystem?: HotkeySystem) {}

public async config(lang: Language | undefined): Promise<HotkeyConfig> {
return expect(
Expand Down Expand Up @@ -63,6 +118,12 @@ class GlobalHotkeys implements HotkeyImplementation {
public resolve(keyCode: string): Promise<string> {
return window.__TAURI__!.core.invoke("resolve_hotkey", { keyCode });
}

public addWindow(_window: Window): void {
// Tauri's hotkeys are global and already receive input regardless of
// which application window has focus. Adding a local listener as well
// would make a popup key press trigger the same command twice.
}
}

export function createHotkeys(
Expand Down Expand Up @@ -100,6 +161,6 @@ export function createHotkeys(
}
return globalHotkeys;
} else {
return hotkeySystem!;
return new LocalHotkeys(hotkeySystem!);
}
}
9 changes: 4 additions & 5 deletions src/ui/LiveSplit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ import { ToastContainer, toast } from "react-toastify";
import * as Storage from "../storage";
import { UrlCache } from "../util/UrlCache";
import {
HotkeySystem_add_window,
ServerProtocol,
TheRunClient,
WebRenderer,
Expand Down Expand Up @@ -1318,6 +1317,10 @@ async function popOut(

childDoc.title = "LiveSplit One";

// Register the window before doing any asynchronous popup setup so its
// shortcuts work immediately, even while fonts and rendering are loading.
hotkeySystem?.addWindow(childWindow);

const link = childDoc.createElement("link");
link.rel = "icon";
link.type = "image/svg+xml";
Expand Down Expand Up @@ -1353,10 +1356,6 @@ async function popOut(
element.style.width = "100%";
element.style.height = "100%";

if (hotkeySystem?.ptr) {
HotkeySystem_add_window(hotkeySystem.ptr, childWindow);
}

createRoot(childDoc.body).render(
<ShowLayout
getState={() => {
Expand Down