The X server, its start and its tools as one module. It provides x11-display with the machine's reach, gated by the host's seat capability. It writes a block at the start of ~/.xinitrc: the account's environment, an explicit import into the user manager, the mesh's X resources merged without cpp, autorandr, the xinitrc slots, ~/.xinitrc.local, and the session's exec from the last slot. Its Go tools serve the seat's displays and layout (autorandr profiles keyed by EDID), and set-mode, primary, dpi, input devices and settings, keyboard, a screenshot (xwd decoded in Go) and the server's log. internal/desktop is how every desktop tool finds the operator's session from the runtime, which has none: from the session's own processes, reading only its words, confirmed with logind.
104 lines
2.8 KiB
Go
104 lines
2.8 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// LogLine is one marked line of the X server's log.
|
|
type LogLine struct {
|
|
At float64 `json:"at_seconds"`
|
|
Level string `json:"level"`
|
|
Text string `json:"text"`
|
|
}
|
|
|
|
// XLog is what x-log answers.
|
|
type XLog struct {
|
|
File string `json:"file"`
|
|
Server string `json:"server,omitempty"`
|
|
Started string `json:"started,omitempty"`
|
|
Errors int `json:"errors"`
|
|
Warnings int `json:"warnings"`
|
|
Lines []LogLine `json:"lines"`
|
|
Cut bool `json:"cut,omitempty"`
|
|
}
|
|
|
|
var (
|
|
logMark = regexp.MustCompile(`^\[\s*([0-9.]+)\] \((EE|WW)\) (.*)$`)
|
|
logStart = regexp.MustCompile(`\(==\) Log file: "[^"]*", Time: (.+)$`)
|
|
)
|
|
|
|
// ParseXLog reads an Xorg log: the server's version, when it started, and its errors and (when
|
|
// asked) warnings, the last `limit` of them. The legend line that explains the markers is not one.
|
|
func ParseXLog(file, text string, warnings bool, limit int) XLog {
|
|
out := XLog{File: file, Lines: []LogLine{}}
|
|
sc := bufio.NewScanner(strings.NewReader(text))
|
|
sc.Buffer(make([]byte, 1<<20), 1<<22)
|
|
for sc.Scan() {
|
|
line := sc.Text()
|
|
if out.Server == "" && strings.HasPrefix(strings.TrimSpace(line), "X.Org X Server ") {
|
|
out.Server = strings.TrimSpace(line)
|
|
}
|
|
if m := logStart.FindStringSubmatch(line); m != nil && out.Started == "" {
|
|
out.Started = m[1]
|
|
}
|
|
m := logMark.FindStringSubmatch(line)
|
|
if m == nil {
|
|
continue
|
|
}
|
|
at, _ := strconv.ParseFloat(m[1], 64)
|
|
level := map[string]string{"EE": "error", "WW": "warning"}[m[2]]
|
|
if level == "error" {
|
|
out.Errors++
|
|
} else {
|
|
out.Warnings++
|
|
if !warnings {
|
|
continue
|
|
}
|
|
}
|
|
out.Lines = append(out.Lines, LogLine{At: at, Level: level, Text: m[3]})
|
|
}
|
|
if limit > 0 && len(out.Lines) > limit {
|
|
out.Lines = out.Lines[len(out.Lines)-limit:]
|
|
out.Cut = true
|
|
}
|
|
return out
|
|
}
|
|
|
|
// XLogFiles are where an X server on display n logs, best first: a root server's log, then a
|
|
// rootless one's under the account's data directory. With no display, every log found, newest first.
|
|
func XLogFiles(display int, home string) []string {
|
|
dirs := []string{"/var/log", filepath.Join(home, ".local/share/xorg")}
|
|
if display >= 0 {
|
|
var out []string
|
|
for _, d := range dirs {
|
|
out = append(out, filepath.Join(d, "Xorg."+strconv.Itoa(display)+".log"))
|
|
}
|
|
return out
|
|
}
|
|
type found struct {
|
|
path string
|
|
mod int64
|
|
}
|
|
var all []found
|
|
for _, d := range dirs {
|
|
matches, _ := filepath.Glob(filepath.Join(d, "Xorg.*.log"))
|
|
for _, p := range matches {
|
|
if info, err := os.Stat(p); err == nil {
|
|
all = append(all, found{p, info.ModTime().UnixNano()})
|
|
}
|
|
}
|
|
}
|
|
sort.Slice(all, func(i, j int) bool { return all[i].mod > all[j].mod })
|
|
var out []string
|
|
for _, f := range all {
|
|
out = append(out, f.path)
|
|
}
|
|
return out
|
|
}
|