73 lines
2.0 KiB
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
|
|
}
|