// 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) }