// SPDX-License-Identifier: GPL-3.0-or-later package remote import ( "context" "git.stormux.org/storm/cthulhu/internal/atspi" "git.stormux.org/storm/cthulhu/internal/wine" "github.com/godbus/dbus/v5" "os" "testing" "time" ) type reviewRecordingHandler struct { *recordingHandler registry *wine.ReviewRegistry } func (h reviewRecordingHandler) RegisterProvider(sender string, path dbus.ObjectPath, version uint32) bool { return h.registry.RegisterProvider(sender, path, version) } func (h reviewRecordingHandler) PresentFocus(sender string, notice wine.ReviewFocusNotice) bool { return h.registry.PresentFocus(sender, notice) } func (h reviewRecordingHandler) BindWineReviewConnection(conn *dbus.Conn) { h.registry.BindWineReviewConnection(conn) } func TestWineReviewAuthorizationAndOptionalHandler(t *testing.T) { r := wine.NewReviewRegistry(nil) endpoint := newEndpoint(nil, reviewRecordingHandler{&recordingHandler{}, r}, func(dbus.Sender) bool { return false }) endpoint.startAccepting() if accepted, err := endpoint.RegisterWineReviewProvider(":1.2", wine.ReviewObjectPath, 1); accepted || err == nil || err.Name != "org.freedesktop.DBus.Error.AccessDenied" { t.Fatal("unauthorized registration accepted") } optional := newEndpoint(nil, &recordingHandler{}, func(dbus.Sender) bool { return true }) optional.startAccepting() if accepted, err := optional.RegisterWineReviewProvider(":1.2", wine.ReviewObjectPath, 1); accepted || err != nil { t.Fatal("older handler compatibility broken") } } type nativeReviewEndpoint struct { conn *dbus.Conn calls chan struct{} bounds atspi.Rectangle } func (f *nativeReviewEndpoint) CaptureWindow(sender dbus.Sender, window, focus string, revision uint64) (map[string]dbus.Variant, *dbus.Error) { var owner string if err := f.conn.BusObject().Call("org.freedesktop.DBus.GetNameOwner", 0, BusName).Store(&owner); err != nil || owner != string(sender) { return nil, dbus.NewError("org.freedesktop.DBus.Error.AccessDenied", nil) } select { case f.calls <- struct{}{}: default: } node := map[string]dbus.Variant{"id": dbus.MakeVariant(window), "parent": dbus.MakeVariant(""), "role": dbus.MakeVariant("window"), "name": dbus.MakeVariant("native fixture"), "protected": dbus.MakeVariant(false), "visible": dbus.MakeVariant(true), "bounds": dbus.MakeVariant(f.bounds), "text_known": dbus.MakeVariant(false), "caret_known": dbus.MakeVariant(false), "caret": dbus.MakeVariant(uint64(0)), "caret_utf16": dbus.MakeVariant(uint64(0)), "ranges": dbus.MakeVariant([]wine.ReviewRange{})} return map[string]dbus.Variant{"version": dbus.MakeVariant(uint32(1)), "instance": dbus.MakeVariant("instance"), "window": dbus.MakeVariant(window), "focus": dbus.MakeVariant(focus), "revision": dbus.MakeVariant(revision), "status": dbus.MakeVariant("ok"), "bounds": dbus.MakeVariant(f.bounds), "nodes": dbus.MakeVariant([]map[string]dbus.Variant{node})}, nil } func TestNativeSessionBusWineReview(t *testing.T) { if os.Getenv("CTHULHU_NATIVE_DBUS_TEST") != "1" { t.Skip("requires private session bus") } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() registry := wine.NewReviewRegistry(nil) if err := registry.Start(ctx); err != nil { t.Fatal(err) } defer registry.Stop(context.Background()) service := New(nil, reviewRecordingHandler{&recordingHandler{}, registry}) if err := service.Start(ctx); err != nil { t.Fatal(err) } defer service.Stop(context.Background()) provider, err := dbus.ConnectSessionBus() if err != nil { t.Fatal(err) } defer provider.Close() fixture := &nativeReviewEndpoint{conn: provider, calls: make(chan struct{}, 1), bounds: atspi.Rectangle{X: -10, Y: 20, Width: 400, Height: 300}} if err = provider.Export(fixture, wine.ReviewObjectPath, wine.ReviewInterface); err != nil { t.Fatal(err) } remote := provider.Object(BusName, ObjectPath) var accepted bool if err = remote.CallWithContext(ctx, InterfaceName+".RegisterWineReviewProvider", 0, wine.ReviewObjectPath, uint32(1)).Store(&accepted); err != nil || !accepted { t.Fatalf("registration: %v %v", accepted, err) } if err = remote.CallWithContext(ctx, InterfaceName+".PresentWineReviewFocus", 0, "instance", "window", "window", uint64(1), "same title", "window", false, fixture.bounds).Store(&accepted); err != nil || !accepted { t.Fatalf("focus: %v %v", accepted, err) } notices := registry.Focuses() if len(notices) != 1 { t.Fatalf("focus mailbox: %+v", notices) } id := notices[0].ID() tree, err := registry.Capture(ctx, id) if err != nil || tree == nil { t.Fatalf("actual owner capture: %v", err) } wrong, err := dbus.ConnectSessionBus() if err != nil { t.Fatal(err) } defer wrong.Close() if err = wrong.Object(id.BusName, wine.ReviewObjectPath).CallWithContext(ctx, wine.ReviewInterface+".CaptureWindow", 0, "window", "window", uint64(1)).Err; err == nil { t.Fatal("different unique connection accepted") } provider.Close() for registry.Current(id) { select { case <-registry.Changes(): case <-ctx.Done(): t.Fatal("dead provider remained current") } } if len(registry.Focuses()) != 0 { t.Fatal("dead focus retained") } }