// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package app import ( "context" "time" "unicode/utf8" ) const phoneticHintDelay = 300 * time.Millisecond // The speech owner retains the hint with its character request. Waiting here // keeps output ordered without blocking input or accessibility event handling; // the normal input, settings, secure-session and shutdown barriers cancel it. func (worker *speechWorker) deliver_phonetic_hint(ctx context.Context, request speechRequest) (bool, error) { if !request.PhoneticHint || !request.Character || request.Role == "password text" || worker.reading == nil || !worker.reading().PhoneticCharacterHints || worker.inputActivity == nil { return false, nil } activity := worker.inputActivity() letter, size := utf8.DecodeRuneInString(request.Text) if !activity.PlainCharacterArrow || size != len(request.Text) || phoneticLetter(letter) == "" { return false, nil } request.PhoneticHint = false if err := worker.deliver(ctx, request); err != nil { return true, err } hintContext, cancelHint := context.WithCancel(ctx) defer cancelHint() worker.mutex.Lock() if worker.generation != request.deliveryGeneration { worker.mutex.Unlock() return true, context.Canceled } worker.hintCancel = cancelHint worker.mutex.Unlock() defer func() { worker.mutex.Lock() worker.hintCancel = nil worker.mutex.Unlock() }() timer := time.NewTimer(phoneticHintDelay) defer timer.Stop() select { case <-hintContext.Done(): return true, hintContext.Err() case <-timer.C: } worker.mutex.Lock() interrupted := worker.generation != request.deliveryGeneration worker.mutex.Unlock() if interrupted || worker.inputActivity().InputSequence != activity.InputSequence || !worker.reading().PhoneticCharacterHints { return true, nil } hint := speechRequest{ // The character already presented its capital cues. This hint only // clarifies its identity, using the normal voice even in links or Wine. Text: phoneticLetter(letter), VoiceType: "default", Role: request.Role, Cause: "phonetic-character-hint", Append: true, Window: request.Window, ExcludeFromHistory: request.ExcludeFromHistory, deliveryGeneration: request.deliveryGeneration, } return true, worker.deliver(hintContext, hint) }