83 lines
2.6 KiB
Go
83 lines
2.6 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
package wine
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"errors"
|
|
"strings"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/atspi"
|
|
"github.com/godbus/dbus/v5"
|
|
)
|
|
|
|
const ReviewProtocolVersion uint32 = 1
|
|
const ReviewObjectPath dbus.ObjectPath = "/org/stormux/Cthulhu1/WineReview"
|
|
const ReviewInterface = "org.stormux.Cthulhu1.WineReview"
|
|
|
|
var (
|
|
ErrReviewUnavailable = errors.New("Wine review unavailable")
|
|
ErrReviewStale = errors.New("Wine review source changed")
|
|
ErrReviewBusy = errors.New("Wine review capture busy")
|
|
ErrReviewLimited = errors.New("Wine review capture exceeds limits")
|
|
ErrReviewInvalid = errors.New("invalid Wine review snapshot")
|
|
)
|
|
|
|
// Notices are copied by the registry. Sender and Path are supplied by actual
|
|
// registration, never by native payloads. The event loop owns their eligibility.
|
|
type ReviewFocusNotice struct {
|
|
Sender string
|
|
Path dbus.ObjectPath
|
|
Instance, Window, Focus string
|
|
Revision uint64
|
|
Title, Role string
|
|
Protected bool
|
|
Bounds atspi.Rectangle
|
|
}
|
|
type ReviewRange struct {
|
|
Start, End, UTF16Start, UTF16End uint64
|
|
Text string
|
|
Bounds atspi.Rectangle
|
|
}
|
|
type ReviewNode struct {
|
|
ID, Parent, Role, Name string
|
|
Protected, Visible, TextKnown, CaretKnown bool
|
|
Caret, CaretUTF16 uint64
|
|
Bounds atspi.Rectangle
|
|
Ranges []ReviewRange
|
|
}
|
|
type ReviewSnapshot struct {
|
|
Version uint32
|
|
Instance, Window, Focus string
|
|
Revision uint64
|
|
Status string
|
|
Bounds atspi.Rectangle
|
|
Nodes []ReviewNode
|
|
}
|
|
|
|
func ReviewID(sender, instance, window, node string) atspi.ObjectID {
|
|
encode := func(value string) string { return hex.EncodeToString([]byte(value)) }
|
|
return atspi.NewObjectID(sender, "/wine/"+encode(instance)+"/"+encode(window)+"/"+encode(node))
|
|
}
|
|
func IsReviewID(id atspi.ObjectID) bool {
|
|
if !id.Valid() || !strings.HasPrefix(id.BusName, ":") {
|
|
return false
|
|
}
|
|
parts := strings.Split(id.Path, "/")
|
|
if len(parts) != 5 || parts[0] != "" || parts[1] != "wine" {
|
|
return false
|
|
}
|
|
for _, part := range parts[2:] {
|
|
if part == "" || len(part) > 512 {
|
|
return false
|
|
}
|
|
if _, err := hex.DecodeString(part); err != nil {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
func (notice ReviewFocusNotice) ID() atspi.ObjectID {
|
|
return ReviewID(notice.Sender, notice.Instance, notice.Window, notice.Focus)
|
|
}
|