Z ZPI 文档 v0.3.8
介绍页

源码

packages/desktop/src/main/shortcut-modifier.ts

typescript67 行查看原文
import type { Input, WebContents } from "electron";

/** One native owner for host and guest hints; it never consumes or dispatches a shortcut. */
export class ShortcutModifier {
  private source?: WebContents;
  private held = false;
  private observers = new Set<() => void>();
  private platform: string;
  private enabled: () => boolean;
  private send: (held: boolean) => void;
  constructor(platform: string, enabled: () => boolean, send: (held: boolean) => void) {
    this.platform = platform;
    this.enabled = enabled;
    this.send = send;
  }
  private update(held: boolean): void {
    if (this.held === held) return;
    this.held = held;
    this.send(held);
  }
  reset = () => {
    this.source = undefined;
    this.update(false);
  };
  resetFor(content: WebContents): void {
    if (this.source === content) this.reset();
  }
  observe(content: WebContents, isHost = false): void {
    // Reloading the host also replaces the IPC consumer, even when a guest owns input.
    const reset = () => (isHost ? this.reset() : this.resetFor(content));
    const input = (
      _: unknown,
      input: Pick<Input, "type"> & Partial<Pick<Input, "meta" | "control" | "modifiers">>,
    ) => {
      if (input.type !== "rawKeyDown" && input.type !== "keyDown" && input.type !== "keyUp") return;
      if (!this.enabled()) return this.reset();
      this.source = content;
      const flag = this.platform === "darwin" ? "meta" : "control";
      const held =
        (this.platform === "darwin" ? input.meta : input.control) ?? input.modifiers?.includes(flag) ?? false;
      this.update(held);
    };
    const dispose = () => {
      content.removeListener("before-input-event", input);
      content.removeListener("input-event", input);
      content.removeListener("did-start-loading", reset);
      content.removeListener("render-process-gone", reset);
      content.removeListener("destroyed", dispose);
      this.observers.delete(dispose);
      reset();
    };
    content.on("before-input-event", input);
    // Chromium can bypass the pre-handler (e.g. input injected through CDP).
    // input-event observes that path; keep before-input-event for consumed shortcuts.
    // Both paths may deliver the same transition, so updates must remain idempotent.
    content.on("input-event", input);
    content.on("did-start-loading", reset);
    content.on("render-process-gone", reset);
    content.on("destroyed", dispose);
    this.observers.add(dispose);
  }
  dispose(): void {
    for (const dispose of this.observers) dispose();
    this.reset();
  }
}