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:
schemaVersion: 1
name: My Wallpaper
slug: my-wallpaper
version: 1.0.0
permissions:
- ime-inputIf 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
| Name | Type | Description |
|---|---|---|
composition | string | null | The in-progress composition text (e.g. "か" "かん" "漢" while typing "漢字"). null when no composition is active. |
cursor | number | Caret position within composition, in code points. |
activate | (anchor?: HTMLElement | { x: number; y: number; height: number }) => void | Give 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 | () => void | Release the proxy window's focus. The hook also calls this automatically on unmount. |
onCommit | (handler) => () => void | Register 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
NSPanelwith.nonactivatingPanelstyle, so the Dock highlight and menu bar do not switch when activated. - Windows: The proxy is a
WS_EX_NOACTIVATEinvisible 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
compositionyourself. - Coordinates passed to
activateare viewport-relative CSS pixels. The backend resolves the wallpaper window's screen position itself, so do not addwindow.screenX/window.screenYmanually — WebKit's values for those properties are unreliable in custom-scheme wallpaper contexts.