// Cthulhu screen reader. // Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package remote import "github.com/godbus/dbus/v5/introspect" // service_bindings builds exports in their existing registration order. Start // performs exports and rollback; Stop reverses this list before releasing names. func service_bindings(endpoint *endpoint, orca bool, handler Handler) []serviceBinding { path, iface := ObjectPath, InterfaceName if orca { path, iface = OrcaObjectPath, OrcaInterfaceName } var mainEndpoint any = endpoint if orca { mainEndpoint = &orcaServiceEndpoint{endpoint: endpoint} } registrations := []serviceBinding{ {path, iface, mainEndpoint}, {path, "org.freedesktop.DBus.Introspectable", introspect.NewIntrospectable(&introspect.Node{ Name: string(path), Interfaces: []introspect.Interface{{Name: iface, Methods: serviceMethods(orca)}}, })}, } if !orca { registrations[1].value = introspect.NewIntrospectable(&introspect.Node{ Name: string(path), Interfaces: []introspect.Interface{{Name: iface, Methods: serviceMethods(false)}}, Children: []introspect.Node{{Name: "SpeechManager"}}, }) registrations = append(registrations, serviceBinding{SpeechManagerPath, SpeechManagerInterface, &speechManagerEndpoint{endpoint: endpoint}}) registrations = append(registrations, serviceBinding{SpeechManagerPath, "org.freedesktop.DBus.Introspectable", introspect.NewIntrospectable(&introspect.Node{ Name: string(SpeechManagerPath), Interfaces: []introspect.Interface{{Name: SpeechManagerInterface, Methods: []introspect.Method{ {Name: "InterruptSpeech", Args: []introspect.Arg{ {Name: "notify_user", Type: "b", Direction: "in"}, {Name: "accepted", Type: "b", Direction: "out"}, }}, }}}, })}) } _, hasModules := handler.(ModuleHandler) if available, ok := handler.(ModuleAvailability); ok && !available.RemoteModulesAvailable() { hasModules = false } if hasModules { children := make([]introspect.Node, 0, len(modules)) for _, spec := range modules { module := &moduleEndpoint{endpoint: endpoint, spec: spec} moduleObjectPath := modulePath(path, spec.name) moduleIface := "org.stormux.Cthulhu1." + spec.name var object any if orca { moduleIface = orcaModuleInterface object = module } else if spec.name == "SpeechManager" { // SpeechManager was registered above for existing self-voice clients. object = nil } else { object = &typingEchoEndpoint{endpoint: endpoint} } if object != nil { registrations = append(registrations, serviceBinding{moduleObjectPath, moduleIface, object}) } registrations = append(registrations, serviceBinding{moduleObjectPath, "org.freedesktop.DBus.Properties", &modulePropertiesEndpoint{module: module, iface: moduleIface}}) introspection := introspect.NewIntrospectable(&introspect.Node{ Name: string(moduleObjectPath), Interfaces: []introspect.Interface{spec.introspection(orca)}, }) if !orca && spec.name == "SpeechManager" { registrations[3].value = introspection } else { registrations = append(registrations, serviceBinding{moduleObjectPath, "org.freedesktop.DBus.Introspectable", introspection}) } children = append(children, introspect.Node{Name: spec.name}) } root := &introspect.Node{Name: string(path), Interfaces: []introspect.Interface{{Name: iface, Methods: serviceMethods(orca)}}, Children: children} registrations[1].value = introspect.NewIntrospectable(root) if orca { registrations[0].value = &orcaServiceEndpoint{endpoint: endpoint, modules: sortedModuleNames()} } } return registrations }