useImeInput

Receives IME-composed text (Japanese, Chinese, Korean, etc.) for wallpapers that want to accept text input — for example a wallpaper-resident notepad, search box, or chat input.

Unlike useKeyboard, which streams raw pre-IME keystrokes from every window on the desktop, useImeInput only delivers text that the user explicitly directs at the wallpaper. Internally Fluxlay creates a tiny invisible proxy window per wallpaper (1×1 px, fully transparent, non-activating). Calling activate() gives that proxy keyboard focus so IME composition is routed through it. The wallpaper window itself never becomes a key window, so the "always-in-background" property of wallpapers is preserved.

Requires the ime-input permission

You must declare ime-input in fluxlay.yaml:

fluxlay.yaml
schemaVersion: 1
name: My Wallpaper
slug: my-wallpaper
version: 1.0.0
permissions:
  - ime-input

If the permission is missing the backend rejects every IME request with HTTP 403. The hook still mounts and exposes the same API, but no streams flow and activate() / deactivate() are accepted by the SDK without effect. See manifest for the full permission list. Note that ime-input and keyboard are intentionally separate: keyboard exposes global keystrokes, while ime-input only sees text directed at the wallpaper itself.

Import

import { useImeInput } from "@fluxlay/react";

Signature

function useImeInput(): ImeInputApi;
 
interface ImeInputApi {
  composition: string | null;
  cursor: number;
  activate: (anchor?: HTMLElement | { x: number; y: number; height: number }) => void;
  deactivate: () => void;
  onCommit: (handler: (text: string) => void) => () => void;
}

Usage

import { useImeInput } from "@fluxlay/react";
import { useEffect, useRef, useState } from "react";
 
function NotePad() {
  const ime = useImeInput();
  const inputRef = useRef<HTMLDivElement>(null);
  const [text, setText] = useState("");
 
  useEffect(() => {
    return ime.onCommit(committed => setText(prev => prev + committed));
  }, [ime]);
 
  return (
    <div
      ref={inputRef}
      tabIndex={0}
      onClick={() => ime.activate(inputRef.current ?? undefined)}
      onBlur={ime.deactivate}
    >
      <span>{text}</span>
      {ime.composition !== null && <span style={{ opacity: 0.5 }}>{ime.composition}</span>}
    </div>
  );
}

API

NameTypeDescription
compositionstring | nullThe in-progress composition text (e.g. "か" "かん" "漢" while typing "漢字"). null when no composition is active.
cursornumberCaret position within composition, in code points.
activate(anchor?: HTMLElement | { x: number; y: number; height: number }) => voidGive the proxy window keyboard focus and start receiving IME input. Pass an HTMLElement (typically your input ref) or an explicit viewport-relative { x, y, height } rect in CSS pixels to position the IME candidate window near the caret. With no argument the candidate appears at the center of the active screen.
deactivate() => voidRelease the proxy window's focus. The hook also calls this automatically on unmount.
onCommit(handler) => () => voidRegister a callback for finalized text (e.g. "漢字"). Returns an unregister function; call it from a useEffect cleanup.

Do not pass a React MouseEvent or other arbitrary object directly to activate — only an HTMLElement or a { x, y, height } rect is interpreted correctly. If you wire up onClick, wrap it (onClick={() => ime.activate(ref.current ?? undefined)}) instead of onClick={ime.activate}.

Interaction with useKeyboard

While activate() is in effect, useKeyboard events are paused on the same wallpaper to prevent IME candidate-window keys (arrows, Enter, Esc) from double-firing as raw key events. They resume normally after deactivate().

Platform notes

  • macOS: The proxy is an NSPanel with .nonactivatingPanel style, so the Dock highlight and menu bar do not switch when activated.
  • Windows: The proxy is a WS_EX_NOACTIVATE invisible HWND, so the taskbar does not flash when focus moves.

Limitations

  • Only the committed string ends up in the wallpaper. To style the in-progress text, render composition yourself.
  • Coordinates passed to activate are viewport-relative CSS pixels. The backend resolves the wallpaper window's screen position itself, so do not add window.screenX / window.screenY manually — WebKit's values for those properties are unreliable in custom-scheme wallpaper contexts.