// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package atspi /* #include "adapter_native.h" #include */ import "C" import ( "context" "errors" "strings" "time" "unicode/utf8" "unsafe" ) // TypeText sends a UTF-8 string to the current input focus. It does not require // an accessible edit control, move focus, or use the clipboard. The host owns // browse-mode policy and must release its modal keyboard capture first. func (adapter *Adapter) TypeText(ctx context.Context, text string) error { if text == "" || utf8.RuneCountInString(text) > 32 || !utf8.ValidString(text) || strings.ContainsRune(text, 0) { return errors.New("invalid typing text") } _, err := adapter.documentOperation(ctx, treeCaptureRequest{typeText: true, insertionText: text}) return err } func (adapter *Adapter) native_type_text(ctx context.Context, text string) error { if ctx.Err() != nil { return ctx.Err() } if adapter.secure.Load() { return errors.New("screen lock active") } // The registry may reuse one temporary X keycode for Unicode characters. // Send one character at a time and pace keymap changes so clients consume // each mapping before the next one. In particular, preserve invisible ZWJs // and variation selectors instead of turning them into the following emoji. for _, character := range text { if err := ctx.Err(); err != nil { return err } if adapter.secure.Load() { return errors.New("screen lock active") } raw := C.CString(string(character)) var nativeErr *C.GError typed := C.atspi_generate_keyboard_event(0, raw, C.ATSPI_KEY_STRING, &nativeErr) C.free(unsafe.Pointer(raw)) if nativeErr != nil { err := nativeError("type text", nativeErr) C.g_error_free(nativeErr) return err } if typed == 0 { return errors.New("text typing failed") } timer := time.NewTimer(20 * time.Millisecond) select { case <-ctx.Done(): timer.Stop() return ctx.Err() case <-timer.C: } } return nil }