LabCodeHub öffentliche Ansicht
Anmelden
Küpper / ehc-tvwall öffentlich
Branch: main
ehc-tvwall / internal / player / display.go
Verlauf Rohdaten
R Rüdiger Küpper fix: v1.0.0
14518d05 vor 11 Tagen
internal/player/display.go 171 Zeilen · 4.9 KB · Go
1
package player
2
3
import (
4
	"fmt"
5
	"log"
6
	"os"
7
	"os/user"
8
	"path/filepath"
9
	"sort"
10
	"strings"
11
)
12
13
// display beschreibt, wohin mpv sein Bild malen soll.
14
type display struct {
15
	mode string   // "drm", "wayland" oder "x11"
16
	env  []string // zusaetzliche Umgebungsvariablen fuer mpv
17
	note string   // Hinweis fuers Protokoll, wenn etwas nicht stimmt
18
}
19
20
// resolveDisplay ermittelt die Bildausgabe.
21
//
22
// DISPLAY_MODE="auto" (Standard) sucht zuerst nach einer laufenden
23
// Desktop-Sitzung des eigenen Benutzers und faellt sonst auf die direkte
24
// Ausgabe ueber DRM/KMS zurueck. "drm" und "desktop" erzwingen die jeweilige
25
// Variante.
26
//
27
// Bei "desktop" ohne erreichbare Sitzung wird ein Fehler geliefert statt auf
28
// DRM auszuweichen: direkt nach dem Einschalten steht die Sitzung oft noch
29
// nicht, und ein Ausweichen wuerde dem gerade startenden Desktop den
30
// Bildschirm wegnehmen. Der Player versucht es dann einfach spaeter erneut.
31
func resolveDisplay(mode string) (display, error) {
32
	switch mode {
33
	case "drm":
34
		d := display{mode: "drm"}
35
		if other := sessionHint(); other != "" {
36
			d.note = "DISPLAY_MODE=drm ist gesetzt, aber es laeuft ein Desktop. " +
37
				"mpv kann den Bildschirm so nicht bekommen. " + other
38
		}
39
		return d, nil
40
41
	case "desktop":
42
		if d := detectSession(); d.mode != "" {
43
			return d, nil
44
		}
45
		msg := "keine erreichbare Desktop-Sitzung gefunden (DISPLAY_MODE=desktop)"
46
		if hint := sessionHint(); hint != "" {
47
			msg += ". " + hint
48
		}
49
		return display{}, fmt.Errorf("%s", msg)
50
51
	default: // "auto" und alles Unbekannte
52
		if d := detectSession(); d.mode != "" {
53
			return d, nil
54
		}
55
		return display{mode: "drm", note: sessionHint()}, nil
56
	}
57
}
58
59
// detectSession sucht die Wayland- bzw. X11-Sitzung des eigenen Benutzers.
60
func detectSession() display {
61
	runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
62
	if runtimeDir == "" {
63
		runtimeDir = fmt.Sprintf("/run/user/%d", os.Getuid())
64
	}
65
	var env []string
66
	if os.Getenv("XDG_RUNTIME_DIR") == "" {
67
		env = append(env, "XDG_RUNTIME_DIR="+runtimeDir)
68
	}
69
70
	// Wayland: labwc, wayfire & Co. legen den Socket im Laufzeitverzeichnis ab.
71
	// Je nach System heisst er wayland-0, wayland-1 - deshalb wird gesucht
72
	// statt geraten.
73
	if name := os.Getenv("WAYLAND_DISPLAY"); name != "" && socketExists(runtimeDir, name) {
74
		return display{mode: "wayland", env: env}
75
	}
76
	if name := findWaylandSocket(runtimeDir); name != "" {
77
		return display{mode: "wayland", env: append(env, "WAYLAND_DISPLAY="+name)}
78
	}
79
80
	// X11
81
	if d := os.Getenv("DISPLAY"); d != "" {
82
		return display{mode: "x11", env: env}
83
	}
84
	if names, _ := filepath.Glob("/tmp/.X11-unix/X*"); len(names) > 0 {
85
		sort.Strings(names)
86
		num := strings.TrimPrefix(filepath.Base(names[0]), "X")
87
		return display{mode: "x11", env: append(env, "DISPLAY=:"+num)}
88
	}
89
90
	return display{}
91
}
92
93
func socketExists(dir, name string) bool {
94
	if filepath.IsAbs(name) {
95
		_, err := os.Stat(name)
96
		return err == nil
97
	}
98
	_, err := os.Stat(filepath.Join(dir, name))
99
	return err == nil
100
}
101
102
func findWaylandSocket(dir string) string {
103
	matches, err := filepath.Glob(filepath.Join(dir, "wayland-*"))
104
	if err != nil {
105
		return ""
106
	}
107
	sort.Strings(matches)
108
	for _, m := range matches {
109
		if strings.HasSuffix(m, ".lock") {
110
			continue
111
		}
112
		if info, err := os.Stat(m); err == nil && info.Mode()&os.ModeSocket != 0 {
113
			return filepath.Base(m)
114
		}
115
	}
116
	return ""
117
}
118
119
// sessionHint schaut nach, ob ein ANDERER Benutzer einen Desktop laufen hat.
120
// Das ist der haeufigste Stolperstein: der Dienst laeuft als "tvwall", der
121
// Desktop gehoert aber "pi" - und auf dessen Sitzung kommt niemand sonst.
122
func sessionHint() string {
123
	me := os.Getuid()
124
	dirs, err := os.ReadDir("/run/user")
125
	if err != nil {
126
		return ""
127
	}
128
	for _, d := range dirs {
129
		if !d.IsDir() || d.Name() == fmt.Sprint(me) {
130
			continue
131
		}
132
		sockets, _ := filepath.Glob(filepath.Join("/run/user", d.Name(), "wayland-*"))
133
		if len(sockets) == 0 {
134
			continue
135
		}
136
		name := d.Name()
137
		if u, err := user.LookupId(d.Name()); err == nil {
138
			name = u.Username
139
		}
140
		return fmt.Sprintf(
141
			"Es laeuft eine Desktop-Sitzung des Benutzers %q, auf die dieser Dienst "+
142
				"keinen Zugriff hat. In /etc/tvwall/tvwall.conf setzen: "+
143
				"RUN_USER=%q und DISPLAY_MODE=\"desktop\", danach "+
144
				"'sudo /opt/tvwall/install.sh --no-ap'.", name, name)
145
	}
146
	return ""
147
}
148
149
// logDisplay schreibt die getroffene Wahl ins Protokoll.
150
func (d display) logDisplay() {
151
	switch d.mode {
152
	case "wayland":
153
		log.Printf("player: Bildausgabe ueber Wayland (%s)", envValue(d.env, "WAYLAND_DISPLAY", os.Getenv("WAYLAND_DISPLAY")))
154
	case "x11":
155
		log.Printf("player: Bildausgabe ueber X11 (%s)", envValue(d.env, "DISPLAY", os.Getenv("DISPLAY")))
156
	default:
157
		log.Printf("player: Bildausgabe direkt ueber DRM/KMS (ohne Desktop)")
158
	}
159
	if d.note != "" {
160
		log.Printf("player: HINWEIS: %s", d.note)
161
	}
162
}
163
164
func envValue(env []string, key, fallback string) string {
165
	for _, e := range env {
166
		if strings.HasPrefix(e, key+"=") {
167
			return strings.TrimPrefix(e, key+"=")
168
		}
169
	}
170
	return fallback
171
}