Report wine-access version if available in version commands.
This commit is contained in:
1 parent
7c032a5f6e
commit
133953e972
10 files changed
+144
-11
No files matched your search
@@ -280,6 +280,13 @@ Helpers built before identity reporting do not provide these identity/status
|
|||||||
records. The installed helper's `--version` option can be used for a manual
|
records. The installed helper's `--version` option can be used for a manual
|
||||||
check; it does not require a running reader.
|
check; it does not require a running reader.
|
||||||
|
|
||||||
|
`cthulhu -v`, `cthulhu --version`, and **Cthulhu+Shift+V** append
|
||||||
|
`wine-access YYYY.MM.DD` when the selected installed helper contains release
|
||||||
|
metadata. This reads its `.exe.so` sidecar directly without starting Wine.
|
||||||
|
Rebuild Wine Access to supply the metadata; older helpers are omitted.
|
||||||
|
The reported release describes the installed files, which can differ from a
|
||||||
|
helper still running after an upgrade.
|
||||||
|
|
||||||
Helper-prefixed stderr diagnostics are captured with a 4096-byte line limit,
|
Helper-prefixed stderr diagnostics are captured with a 4096-byte line limit,
|
||||||
a 64-record queue and up to 32 distinct records per ten-second window. Repeated,
|
a 64-record queue and up to 32 distinct records per ten-second window. Repeated,
|
||||||
oversized and excess records are counted in a suppression notice. Unrelated
|
oversized and excess records are counted in a suppression notice. Unrelated
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import (
|
|||||||
"git.stormux.org/storm/cthulhu/internal/speechhistoryui"
|
"git.stormux.org/storm/cthulhu/internal/speechhistoryui"
|
||||||
"git.stormux.org/storm/cthulhu/internal/tentacle"
|
"git.stormux.org/storm/cthulhu/internal/tentacle"
|
||||||
"git.stormux.org/storm/cthulhu/internal/version"
|
"git.stormux.org/storm/cthulhu/internal/version"
|
||||||
|
"git.stormux.org/storm/cthulhu/internal/wine"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -95,6 +96,7 @@ func run(ctx context.Context, arguments []string, stdout, stderr io.Writer) (res
|
|||||||
atspi.Version(),
|
atspi.Version(),
|
||||||
os.Getenv("XDG_SESSION_TYPE"),
|
os.Getenv("XDG_SESSION_TYPE"),
|
||||||
os.Getenv("XDG_SESSION_DESKTOP"),
|
os.Getenv("XDG_SESSION_DESKTOP"),
|
||||||
|
wine.InstalledVersion(),
|
||||||
))
|
))
|
||||||
fmt.Fprintln(stdout)
|
fmt.Fprintln(stdout)
|
||||||
fmt.Fprintln(stdout, "Usage: cthulhu [options]")
|
fmt.Fprintln(stdout, "Usage: cthulhu [options]")
|
||||||
@@ -132,6 +134,7 @@ func run(ctx context.Context, arguments []string, stdout, stderr io.Writer) (res
|
|||||||
atspi.Version(),
|
atspi.Version(),
|
||||||
os.Getenv("XDG_SESSION_TYPE"),
|
os.Getenv("XDG_SESSION_TYPE"),
|
||||||
os.Getenv("XDG_SESSION_DESKTOP"),
|
os.Getenv("XDG_SESSION_DESKTOP"),
|
||||||
|
wine.InstalledVersion(),
|
||||||
))
|
))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,21 +26,22 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"git.stormux.org/storm/cthulhu/internal/atspi"
|
||||||
"git.stormux.org/storm/cthulhu/internal/version"
|
"git.stormux.org/storm/cthulhu/internal/version"
|
||||||
|
"git.stormux.org/storm/cthulhu/internal/wine"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestVersion(t *testing.T) {
|
func TestVersion(t *testing.T) {
|
||||||
t.Setenv("XDG_SESSION_TYPE", "x11")
|
t.Setenv("XDG_SESSION_TYPE", "x11")
|
||||||
t.Setenv("XDG_SESSION_DESKTOP", "GNOME")
|
t.Setenv("XDG_SESSION_DESKTOP", "GNOME")
|
||||||
want := version.Details("2.58.0", "x11", "GNOME")
|
want := version.Details(atspi.Version(), "x11", "GNOME", wine.InstalledVersion())
|
||||||
for _, argument := range []string{"--version", "-v"} {
|
for _, argument := range []string{"--version", "-v"} {
|
||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
if err := run(context.Background(), []string{argument}, &stdout, &stdout); err != nil {
|
if err := run(context.Background(), []string{argument}, &stdout, &stdout); err != nil {
|
||||||
t.Fatalf("run %s: %v", argument, err)
|
t.Fatalf("run %s: %v", argument, err)
|
||||||
}
|
}
|
||||||
if got := strings.TrimSpace(stdout.String()); !strings.Contains(got, "Cthulhu screen reader version") ||
|
if got := strings.TrimSpace(stdout.String()); got != want {
|
||||||
!strings.Contains(got, "AT-SPI2 version:") || !strings.Contains(got, "Session: x11 GNOME") {
|
t.Fatalf("version output = %q, want %q", got, want)
|
||||||
t.Fatalf("version output = %q, want details including %q", got, want)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
pkgbase=cthulhu-git
|
pkgbase=cthulhu-git
|
||||||
pkgname=cthulhu-git
|
pkgname=cthulhu-git
|
||||||
_pkgname=cthulhu
|
_pkgname=cthulhu
|
||||||
pkgver=2026.10.23.r73.g03e2b31
|
pkgver=2026.10.24.r75.g8a560a2
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Desktop-agnostic screen reader with speech, Braille, and Tentacle support"
|
pkgdesc="Desktop-agnostic screen reader with speech, Braille, and Tentacle support"
|
||||||
url="https://git.stormux.org/storm/cthulhu"
|
url="https://git.stormux.org/storm/cthulhu"
|
||||||
|
|||||||
@@ -4,11 +4,14 @@
|
|||||||
|
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import "git.stormux.org/storm/cthulhu/internal/version"
|
import (
|
||||||
|
"git.stormux.org/storm/cthulhu/internal/version"
|
||||||
|
"git.stormux.org/storm/cthulhu/internal/wine"
|
||||||
|
)
|
||||||
|
|
||||||
func presentVersion(speech speechEnqueuer, atspiVersion, sessionType, sessionDesktop string) {
|
func presentVersion(speech speechEnqueuer, atspiVersion, sessionType, sessionDesktop string) {
|
||||||
speech.Enqueue(speechRequest{
|
speech.Enqueue(speechRequest{
|
||||||
Text: version.Details(atspiVersion, sessionType, sessionDesktop),
|
Text: version.Details(atspiVersion, sessionType, sessionDesktop, wine.InstalledVersion()),
|
||||||
Role: "system",
|
Role: "system",
|
||||||
Cause: "version",
|
Cause: "version",
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.stormux.org/storm/cthulhu/internal/version"
|
"git.stormux.org/storm/cthulhu/internal/version"
|
||||||
|
"git.stormux.org/storm/cthulhu/internal/wine"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPresentVersion(t *testing.T) {
|
func TestPresentVersion(t *testing.T) {
|
||||||
@@ -23,7 +24,7 @@ func TestPresentVersion(t *testing.T) {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
request := <-speech.requests
|
request := <-speech.requests
|
||||||
want := version.Details("2.58.0", "x11", "GNOME")
|
want := version.Details("2.58.0", "x11", "GNOME", wine.InstalledVersion())
|
||||||
if request.Text != want || request.Role != "system" || request.Cause != "version" {
|
if request.Text != want || request.Role != "system" || request.Cause != "version" {
|
||||||
t.Fatalf("request = %+v, want %q as system version", request, want)
|
t.Fatalf("request = %+v, want %q as system version", request, want)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,11 +30,14 @@ func String() string {
|
|||||||
return Name + " screen reader version " + Build + "-" + Channel
|
return Name + " screen reader version " + Build + "-" + Channel
|
||||||
}
|
}
|
||||||
|
|
||||||
func Details(atspiVersion, sessionType, sessionDesktop string) string {
|
func Details(atspiVersion, sessionType, sessionDesktop, wineVersion string) string {
|
||||||
details := String() + ", AT-SPI2 version: " + atspiVersion
|
details := String() + ", AT-SPI2 version: " + atspiVersion
|
||||||
session := strings.TrimSpace(sessionType + " " + sessionDesktop)
|
session := strings.TrimSpace(sessionType + " " + sessionDesktop)
|
||||||
if session != "" {
|
if session != "" {
|
||||||
details += ", Session: " + session
|
details += ", Session: " + session
|
||||||
}
|
}
|
||||||
|
if wineVersion != "" {
|
||||||
|
details += ", wine-access " + wineVersion
|
||||||
|
}
|
||||||
return details
|
return details
|
||||||
}
|
}
|
||||||
@@ -42,12 +42,22 @@ func TestStringUsesCurrentVersionByDefault(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDetailsIncludesATSPIAndSession(t *testing.T) {
|
func TestDetailsIncludesATSPIAndSession(t *testing.T) {
|
||||||
got := Details("2.58.0", "x11", "GNOME")
|
got := Details("2.58.0", "x11", "GNOME", "")
|
||||||
want := "Cthulhu screen reader version " + Version + "-" + Channel + ", AT-SPI2 version: 2.58.0, Session: x11 GNOME"
|
want := "Cthulhu screen reader version " + Version + "-" + Channel + ", AT-SPI2 version: 2.58.0, Session: x11 GNOME"
|
||||||
if got != want {
|
if got != want {
|
||||||
t.Fatalf("Details() = %q, want %q", got, want)
|
t.Fatalf("Details() = %q, want %q", got, want)
|
||||||
}
|
}
|
||||||
if Details("2.58.0", " ", "") != strings.TrimSuffix(got, ", Session: x11 GNOME") {
|
if Details("2.58.0", " ", "", "") != strings.TrimSuffix(got, ", Session: x11 GNOME") {
|
||||||
t.Fatal("empty session information was included")
|
t.Fatal("empty session information was included")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDetailsIncludesOptionalWineAccess(t *testing.T) {
|
||||||
|
base := Details("2.58.0", "x11", "GNOME", "")
|
||||||
|
if got := Details("2.58.0", "x11", "GNOME", "2026.10.24"); got != base+", wine-access 2026.10.24" {
|
||||||
|
t.Fatalf("Details() = %q", got)
|
||||||
|
}
|
||||||
|
if got := Details("2.58.0", "x11", "GNOME", ""); got != base {
|
||||||
|
t.Fatalf("missing helper changed details: %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package wine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"debug/elf"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InstalledVersion reads the selected helper's embedded release without
|
||||||
|
// executing Wine. Older helpers without the metadata return an empty string.
|
||||||
|
// This describes the files on disk, which may differ from a running helper.
|
||||||
|
func InstalledVersion() string {
|
||||||
|
helper := helperPath()
|
||||||
|
if helper == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
// Resolve launcher symlinks before locating its adjacent Winelib sidecar.
|
||||||
|
helper, err := filepath.EvalSymlinks(helper)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
file, err := elf.Open(helper + ".so")
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
section := file.Section(".wine_access.version")
|
||||||
|
if section == nil || section.Size != 11 || section.Flags&elf.SHF_COMPRESSED != 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var data [11]byte
|
||||||
|
if _, err := section.ReadAt(data[:], 0); err != nil || data[10] != 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
release := string(data[:10])
|
||||||
|
if _, err := time.Parse("2006.01.02", release); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return release
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package wine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInstalledVersion(t *testing.T) {
|
||||||
|
root := t.TempDir()
|
||||||
|
helper := filepath.Join(root, "wine-access.exe")
|
||||||
|
// The launcher must never run, including for old helpers lacking metadata.
|
||||||
|
marker := filepath.Join(root, "executed")
|
||||||
|
if err := os.WriteFile(helper, []byte("#!/bin/sh\ntouch '"+marker+"'\nexit 99\n"), 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Setenv("CTHULHU_WINE_ACCESS_HELPER", helper)
|
||||||
|
for _, test := range []struct {
|
||||||
|
name, declaration, want string
|
||||||
|
}{
|
||||||
|
{"current", `const char release[] = "2026.10.24";`, "2026.10.24"},
|
||||||
|
{"old helper", `int unused;`, ""},
|
||||||
|
{"invalid date", `const char release[] = "2026.99.99";`, ""},
|
||||||
|
{"oversized", `const char release[] = "2026.10.24 extra";`, ""},
|
||||||
|
{"unterminated", `const char release[11] = "2026.10.24x";`, ""},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
source := "__attribute__((used, section(\".wine_access.version\")))\n" + test.declaration
|
||||||
|
if test.name == "old helper" {
|
||||||
|
source = test.declaration
|
||||||
|
}
|
||||||
|
command := exec.Command("cc", "-shared", "-fPIC", "-x", "c", "-o", helper+".so", "-")
|
||||||
|
command.Stdin = strings.NewReader(source)
|
||||||
|
if output, err := command.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("compile helper metadata fixture: %v\n%s", err, output)
|
||||||
|
}
|
||||||
|
if got := InstalledVersion(); got != test.want {
|
||||||
|
t.Fatalf("InstalledVersion() = %q, want %q", got, test.want)
|
||||||
|
}
|
||||||
|
link := filepath.Join(t.TempDir(), "wine-access.exe")
|
||||||
|
if err := os.Symlink(helper, link); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Setenv("CTHULHU_WINE_ACCESS_HELPER", link)
|
||||||
|
if got := InstalledVersion(); got != test.want {
|
||||||
|
t.Fatalf("symlink InstalledVersion() = %q, want %q", got, test.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(marker); !os.IsNotExist(err) {
|
||||||
|
t.Fatalf("version detection executed the helper: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(helper+".so", []byte("broken sidecar"), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got := InstalledVersion(); got != "" {
|
||||||
|
t.Fatalf("invalid ELF returned %q", got)
|
||||||
|
}
|
||||||
|
t.Setenv("CTHULHU_WINE_ACCESS_HELPER", "/usr/bin/true")
|
||||||
|
if got := InstalledVersion(); got != "" {
|
||||||
|
t.Fatalf("missing sidecar returned %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user