70 lines
2.6 KiB
Go
70 lines
2.6 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package atspi
|
|
|
|
// IsEchoEditField requires positive editable metadata. Text roles alone also
|
|
// describe labels and read-only documents; terminals are not edit fields.
|
|
func IsEchoEditField(snapshot Snapshot) bool {
|
|
return snapshot.Editable && !snapshot.ReadOnly && !snapshot.Defunct &&
|
|
snapshot.Role != "password text" && snapshot.Role != "terminal"
|
|
}
|
|
|
|
type echoFocus struct {
|
|
object, origin, window ObjectID
|
|
epoch uint64
|
|
editable bool
|
|
}
|
|
|
|
// EditFieldFocused reads copied metadata only. The GLib owner publishes it;
|
|
// foreground epochs reject stale focus even before queued events are consumed.
|
|
func (adapter *Adapter) EditFieldFocused() bool {
|
|
focus := adapter.echoFocus.Load()
|
|
foreground := adapter.Foreground()
|
|
return !adapter.secure.Load() && focus != nil && focus.editable &&
|
|
focus.epoch == foreground.Epoch && (!foreground.Client.Known || foreground.Client.Window != 0)
|
|
}
|
|
|
|
func (adapter *Adapter) observeEchoFocus(event Event) {
|
|
if event.ForegroundMismatch || event.Foreground.Epoch != adapter.Foreground().Epoch {
|
|
return
|
|
}
|
|
current := adapter.echoFocus.Load()
|
|
if event.Type == WindowActivatedEvent && !event.Source.Focused {
|
|
if current != nil && event.Window != current.window {
|
|
adapter.echoFocus.Store(nil)
|
|
}
|
|
return
|
|
}
|
|
gain := event.Type == FocusEvent || event.Type == FocusedStateEvent && event.Detail1 != 0 ||
|
|
event.Type == WindowActivatedEvent && event.Source.Focused
|
|
if !gain && (current == nil || current.object != event.Source.ID()) {
|
|
return
|
|
}
|
|
if !event.Source.ID().Valid() {
|
|
return
|
|
}
|
|
origin := NewObjectID(event.Source.BusName, event.OriginPath)
|
|
if !gain && current != nil {
|
|
origin = current.origin
|
|
}
|
|
adapter.echoFocus.Store(&echoFocus{object: event.Source.ID(), origin: origin,
|
|
window: event.Window, epoch: event.Foreground.Epoch,
|
|
editable: IsEchoEditField(event.Source) && event.Source.Focused})
|
|
}
|
|
|
|
// Retire before filtering focus losses or dead objects out of speech delivery.
|
|
func (adapter *Adapter) retireEchoFocus(kind string, detail int, source ObjectID) {
|
|
current := adapter.echoFocus.Load()
|
|
if current == nil {
|
|
return
|
|
}
|
|
matched := source == current.object || source == current.origin || source == current.window
|
|
if (kind == FocusedStateEvent && detail == 0 || kind == DefunctStateEvent && detail != 0 ||
|
|
kind == WindowDestroyedEvent || kind == WindowDeactivatedEvent) && matched ||
|
|
(kind == FocusEvent || kind == FocusedStateEvent && detail != 0) && source.BusName == current.object.BusName {
|
|
adapter.echoFocus.Store(nil)
|
|
}
|
|
}
|