Files
cthulhu/internal/atspi/text_input_native.go

73 lines
2.0 KiB
Go

// 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 <stdlib.h>
*/
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
}