Cthulhu is no longer a fork. Now it is a screen reader written in Golang.
This commit is contained in:
1 parent
a998fe67de
commit
069fc674dc
708 files changed
+112482
-532
No files matched your search
@@ -0,0 +1,135 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// Authors: Storm Dragon, Angus Kola
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package preferences
|
||||
|
||||
/*
|
||||
#cgo pkg-config: atspi-2
|
||||
#include <stdlib.h>
|
||||
#include "native_applications.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"git.stormux.org/storm/cthulhu/internal/platform/xorg"
|
||||
)
|
||||
|
||||
type applicationIdentity struct {
|
||||
name string
|
||||
pid int
|
||||
}
|
||||
|
||||
func application_choices(accessible []applicationIdentity, windows []xorg.ApplicationWindow) []string {
|
||||
names := make(map[string]bool)
|
||||
byPID := make(map[int]string)
|
||||
add := func(name string) {
|
||||
name = strings.TrimSpace(name)
|
||||
if name != "" && !strings.HasPrefix(strings.ToLower(name), "cthulhu") {
|
||||
names[name] = true
|
||||
}
|
||||
}
|
||||
for _, application := range accessible {
|
||||
add(application.name)
|
||||
if application.pid != 0 {
|
||||
byPID[application.pid] = application.name
|
||||
}
|
||||
}
|
||||
for _, window := range windows {
|
||||
name := window.Application
|
||||
if !window.Wine && byPID[window.PID] != "" {
|
||||
name = byPID[window.PID]
|
||||
}
|
||||
add(name)
|
||||
}
|
||||
var choices []string
|
||||
for name := range names {
|
||||
choices = append(choices, name)
|
||||
}
|
||||
sort.Slice(choices, func(i, j int) bool { return strings.ToLower(choices[i]) < strings.ToLower(choices[j]) })
|
||||
return choices
|
||||
}
|
||||
|
||||
func running_applications() []string {
|
||||
var count C.int
|
||||
native := C.prefs_application_names(&count)
|
||||
defer C.prefs_free_application_names(native, count)
|
||||
var accessible []applicationIdentity
|
||||
for _, application := range unsafe.Slice(native, int(count)) {
|
||||
name := C.GoString(application.name)
|
||||
if strings.TrimSpace(name) == "" && application.pid != 0 {
|
||||
// Match the reader's process-name fallback for nameless providers.
|
||||
if content, err := os.ReadFile(fmt.Sprintf("/proc/%d/comm", application.pid)); err == nil {
|
||||
name = strings.TrimSpace(string(content))
|
||||
}
|
||||
}
|
||||
accessible = append(accessible, applicationIdentity{name, int(application.pid)})
|
||||
}
|
||||
reader := xorg.OpenForegroundReader()
|
||||
defer reader.Close()
|
||||
return application_choices(accessible, reader.ApplicationWindows())
|
||||
}
|
||||
|
||||
// The chooser shares the preferences child and bounded pipe protocol. Cancel
|
||||
// emits no request, so it cannot open a profile or submit settings.
|
||||
func choose_application(ctx context.Context, updates <-chan Response, errorsIn <-chan error, output io.Writer) (Response, bool, error) {
|
||||
choices := running_applications()
|
||||
if C.prefs_application_chooser_init() == 0 {
|
||||
return Response{}, false, errors.New("cannot open application chooser on this display")
|
||||
}
|
||||
defer C.prefs_application_chooser_close()
|
||||
for _, choice := range choices {
|
||||
name := C.CString(choice)
|
||||
C.prefs_application_chooser_add(name)
|
||||
C.free(unsafe.Pointer(name))
|
||||
}
|
||||
C.prefs_application_chooser_present()
|
||||
ticker := time.NewTicker(time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
selected := false
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return Response{}, false, ctx.Err()
|
||||
case err := <-errorsIn:
|
||||
return Response{}, false, err
|
||||
case response := <-updates:
|
||||
if response.Present {
|
||||
C.prefs_application_chooser_present()
|
||||
} else if selected {
|
||||
return response, true, nil
|
||||
}
|
||||
case <-ticker.C:
|
||||
C.prefs_application_chooser_iterate()
|
||||
switch C.prefs_application_chooser_action() {
|
||||
case 2:
|
||||
return Response{}, false, nil
|
||||
case 1:
|
||||
if selected {
|
||||
continue
|
||||
}
|
||||
name := C.prefs_application_chooser_selected()
|
||||
if name == nil {
|
||||
continue
|
||||
}
|
||||
application := C.GoString(name)
|
||||
C.free(unsafe.Pointer(name))
|
||||
if err := json.NewEncoder(output).Encode(Request{Application: application}); err != nil {
|
||||
return Response{}, false, err
|
||||
}
|
||||
selected = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user