131 lines
4.6 KiB
Go
131 lines
4.6 KiB
Go
// Cthulhu screen reader.
|
|
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify it
|
|
// under the terms of the GNU General Public License as published by the Free
|
|
// Software Foundation, either version 3 of the License, or (at your option)
|
|
// any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
// more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License along with
|
|
// this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
|
|
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"strconv"
|
|
"time"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/lifecycle"
|
|
"git.stormux.org/storm/cthulhu/internal/nvda2cthulhu"
|
|
"git.stormux.org/storm/cthulhu/internal/remote"
|
|
"git.stormux.org/storm/cthulhu/internal/wine"
|
|
)
|
|
|
|
// remoteServiceDependencies are held by the service closures for the lifetime
|
|
// of the runner. The input and adapter counters are read only on snapshot calls.
|
|
type remoteServiceDependencies struct {
|
|
logger *slog.Logger
|
|
stopRun context.CancelFunc
|
|
adapter Adapter
|
|
inputListener InputListener
|
|
speech *speechWorker
|
|
braille *brailleWorker
|
|
live *liveSettings
|
|
preferences *preferencesWorker
|
|
onPreferences func()
|
|
}
|
|
|
|
func newRemoteRuntimeServices(deps remoteServiceDependencies) []lifecycle.Service {
|
|
remoteHandler := remoteSpeech{braille: lockFeedbackBraille{deps.braille}}
|
|
remoteHandler.live = deps.live
|
|
remoteHandler.preferences = deps.preferences
|
|
remoteHandler.showPreferences = deps.onPreferences
|
|
remoteHandler.quit = func() { time.AfterFunc(100*time.Millisecond, deps.stopRun) }
|
|
remoteHandler.runtimeSnapshot = func() string {
|
|
snapshot := "CTHULHU RUNTIME STATE SNAPSHOT\n[event_manager]\noverflow: " + strconv.FormatUint(deps.adapter.Overflow(), 10)
|
|
if deps.inputListener != nil {
|
|
snapshot += "\n[input_event_manager]\noverflow: " + strconv.FormatUint(deps.inputListener.Overflow(), 10)
|
|
}
|
|
return snapshot
|
|
}
|
|
if deps.speech != nil {
|
|
remoteHandler.speech = deps.speech
|
|
}
|
|
wineEvents := newWineEventPresenter(func(message string) bool {
|
|
return remoteHandler.presentMessage(message, "wine-accessible-event")
|
|
})
|
|
wineEvents.logger = deps.logger
|
|
connectWineWindowTitles(wineEvents, deps.live, deps.adapter)
|
|
remoteHandler.wine = wineEvents
|
|
if deps.live != nil {
|
|
remoteHandler.wineReview = deps.live.wineReview
|
|
}
|
|
remoteService := remote.New(deps.logger, remoteHandler)
|
|
services := []lifecycle.Service{{
|
|
Name: "self-voice D-Bus",
|
|
Start: func(ctx context.Context) error {
|
|
if err := remoteService.Start(ctx); err != nil {
|
|
deps.logger.Warn("Cthulhu D-Bus service unavailable", "error", err)
|
|
}
|
|
return nil
|
|
},
|
|
Stop: func(ctx context.Context) error {
|
|
wineEvents.Close()
|
|
if err := remoteService.Stop(ctx); err != nil {
|
|
deps.logger.Warn("Cthulhu D-Bus service shutdown", "error", err)
|
|
}
|
|
return nil
|
|
},
|
|
}}
|
|
if remoteHandler.wineReview != nil {
|
|
registry := remoteHandler.wineReview
|
|
// Reverse shutdown cancels captures before the borrowed service bus closes.
|
|
services = append(services, lifecycle.Service{Name: "Wine flat review", Start: func(ctx context.Context) error {
|
|
if err := registry.Start(ctx); err != nil {
|
|
deps.logger.Warn("Wine flat review unavailable", "error", err)
|
|
}
|
|
return nil
|
|
}, Stop: registry.Stop})
|
|
}
|
|
orcaService := remote.NewOrca(deps.logger, remoteHandler)
|
|
services = append(services, lifecycle.Service{
|
|
Name: "Orca compatibility D-Bus",
|
|
Start: func(ctx context.Context) error {
|
|
if err := orcaService.Start(ctx); err != nil {
|
|
deps.logger.Warn("Orca compatibility D-Bus service unavailable", "error", err)
|
|
}
|
|
return nil
|
|
},
|
|
Stop: func(ctx context.Context) error {
|
|
if err := orcaService.Stop(ctx); err != nil {
|
|
deps.logger.Warn("Orca compatibility D-Bus service shutdown", "error", err)
|
|
}
|
|
return nil
|
|
},
|
|
})
|
|
bridgeServer := nvda2cthulhu.New(deps.logger, remoteHandler)
|
|
services = append(services, lifecycle.Service{
|
|
Name: "NVDA to Cthulhu bridge",
|
|
Start: func(ctx context.Context) error {
|
|
if err := bridgeServer.Start(ctx); err != nil {
|
|
deps.logger.Warn("NVDA to Cthulhu bridge unavailable", "error", err)
|
|
}
|
|
return nil
|
|
},
|
|
Stop: bridgeServer.Stop,
|
|
})
|
|
wineManager := wine.New(deps.logger)
|
|
services = append(services, lifecycle.Service{
|
|
Name: "Wine accessibility", Start: wineManager.Start, Stop: wineManager.Stop,
|
|
})
|
|
return services
|
|
}
|