feature: Add basic EDID reading, writing, and patching capabilities

This commit is contained in:
Tera << 8 2025-06-22 20:14:04 -04:00
parent d0a4d26082
commit 183d2606dc
Signed by: imterah
GPG key ID: 8FA7DD57BA6CEA37
12 changed files with 431 additions and 5 deletions

View file

@ -1,4 +1,4 @@
package main
package config
import _ "embed"

View file

@ -0,0 +1,72 @@
package edidtools
import (
"fmt"
edidparser "github.com/anoopengineer/edidparser/edid"
)
func ParseEDID(rawEDIDFile []byte, allowUnsupportedDevices bool) (*DisplayMetadata, error) {
parsedEDID, err := edidparser.NewEdid(rawEDIDFile)
if err != nil {
return nil, fmt.Errorf("failed to parse EDID file: %w", err)
}
for manufacturer, manufacturerSupportedDevices := range QuirksRegistry {
if parsedEDID.ManufacturerId == manufacturer {
if deviceQuirks, ok := manufacturerSupportedDevices[parsedEDID.MonitorName]; ok || allowUnsupportedDevices {
maxWidth := 0
maxHeight := 0
maxRefreshRate := 0
for _, resolution := range parsedEDID.DetailedTimingDescriptors {
if int(resolution.HorizontalActive) > maxWidth && int(resolution.VerticalActive) > maxHeight {
maxWidth = int(resolution.HorizontalActive)
maxHeight = int(resolution.VerticalActive)
}
// Convert pixel clock to refresh rate
// Refresh Rate = Pixel Clock / ((Horizontal Active + Horizontal Blanking) * (Vertical Active + Vertical Blanking))
hTotal := int(resolution.HorizontalActive + resolution.HorizontalBlanking)
vTotal := int(resolution.VerticalActive + resolution.VerticalBlanking)
refreshRate := int(int(resolution.PixelClock*1000) / (hTotal * vTotal))
if refreshRate > maxRefreshRate {
maxRefreshRate = refreshRate
}
}
if maxWidth == 0 || maxHeight == 0 {
if deviceQuirks.MaxWidth == 0 || deviceQuirks.MaxHeight == 0 {
return nil, fmt.Errorf("failed to determine maximum resolution for monitor '%s'", parsedEDID.MonitorName)
}
maxWidth = deviceQuirks.MaxWidth
maxHeight = deviceQuirks.MaxHeight
}
if maxRefreshRate == 0 {
if deviceQuirks.MaxRefreshRate == 0 {
return nil, fmt.Errorf("failed to determine maximum refresh rate for monitor '%s'", parsedEDID.MonitorName)
}
maxRefreshRate = deviceQuirks.MaxRefreshRate
}
displayMetadata := &DisplayMetadata{
EDID: rawEDIDFile,
DeviceVendor: parsedEDID.ManufacturerId,
DeviceQuirks: deviceQuirks,
MaxWidth: maxWidth,
MaxHeight: maxHeight,
MaxRefreshRate: maxRefreshRate,
}
return displayMetadata, nil
}
}
}
return nil, fmt.Errorf("failed to match manufacturer for monitor vendor: '%s'", parsedEDID.ManufacturerId)
}

View file

@ -0,0 +1,127 @@
//go:build linux
// +build linux
package edidtools
import (
"fmt"
"os"
"os/exec"
"strings"
"github.com/charmbracelet/log"
)
// Attempts to fetch the EDID firmware for any supported XR glasses device
func FetchXRGlassEDID(allowUnsupportedDevices bool) (*DisplayMetadata, error) {
// Implementation goes here
pciDeviceCommand, err := exec.Command("lspci").Output()
if err != nil {
return nil, fmt.Errorf("failed to execute lspci command: %w", err)
}
pciDevices := strings.Split(string(pciDeviceCommand), "\n")
pciDevices = pciDevices[:len(pciDevices)-1]
vgaDevices := []string{}
for _, pciDevice := range pciDevices {
if strings.Contains(pciDevice, "VGA compatible controller:") {
vgaDevices = append(vgaDevices, pciDevice[:strings.Index(pciDevice, " ")])
}
}
for _, vgaDevice := range vgaDevices {
cardDevices, err := os.ReadDir("/sys/devices/pci0000:00/0000:" + vgaDevice + "/drm/")
if err != nil {
return nil, fmt.Errorf("failed to read directory for device '%s': %w", vgaDevice, err)
}
for _, cardDevice := range cardDevices {
if !strings.Contains(cardDevice.Name(), "card") {
continue
}
monitors, err := os.ReadDir("/sys/devices/pci0000:00/0000:" + vgaDevice + "/drm/" + cardDevice.Name())
if err != nil {
return nil, fmt.Errorf("failed to read directory for card device '%s': %w", cardDevice.Name(), err)
}
for _, monitor := range monitors {
if !strings.Contains(monitor.Name(), cardDevice.Name()) {
continue
}
rawEDIDFile, err := os.ReadFile("/sys/devices/pci0000:00/0000:" + vgaDevice + "/drm/" + cardDevice.Name() + "/" + monitor.Name() + "/edid")
if err != nil {
return nil, fmt.Errorf("failed to read EDID file for monitor '%s': %w", monitor.Name(), err)
}
if len(rawEDIDFile) == 0 {
continue
}
parsedEDID, err := ParseEDID(rawEDIDFile, allowUnsupportedDevices)
if err != nil {
if !strings.HasPrefix(err.Error(), "failed to match manufacturer for monitor vendor") {
log.Warnf("Failed to parse EDID for monitor '%s': %s", monitor.Name(), err.Error())
}
} else {
parsedEDID.LinuxDRMCard = cardDevice.Name()
parsedEDID.LinuxDRMConnector = strings.Replace(cardDevice.Name(), cardDevice.Name()+"-", "", 1)
return parsedEDID, nil
}
}
}
}
return nil, fmt.Errorf("could not find supported device! Check if the XR device is plugged in. If it is plugged in and working correctly, check the README or open an issue.")
}
// Loads custom firmware for a supported XR glass device
func LoadCustomEDIDFirmware(displayMetadata *DisplayMetadata, edidFirmware []byte) error {
if displayMetadata.LinuxDRMCard == "" || displayMetadata.LinuxDRMConnector == "" {
return fmt.Errorf("missing Linux DRM card or connector information")
}
drmFile, err := os.Open("/sys/kernel/debug/dri/" + strings.Replace(displayMetadata.LinuxDRMCard, "card", "", 1) + "/" + displayMetadata.LinuxDRMConnector + "/edid_override")
if err != nil {
return fmt.Errorf("failed to open EDID override file for monitor '%s': %w", displayMetadata.LinuxDRMConnector, err)
}
defer drmFile.Close()
if _, err := drmFile.Write(edidFirmware); err != nil {
return fmt.Errorf("failed to write EDID firmware for monitor '%s': %w", displayMetadata.LinuxDRMConnector, err)
}
return nil
}
// Unloads custom firmware for a supported XR glass device
func UnloadCustomEDIDFirmware(displayMetadata *DisplayMetadata) error {
if displayMetadata.LinuxDRMCard == "" || displayMetadata.LinuxDRMConnector == "" {
return fmt.Errorf("missing Linux DRM card or connector information")
}
drmFile, err := os.Open("/sys/kernel/debug/dri/" + strings.Replace(displayMetadata.LinuxDRMCard, "card", "", 1) + "/" + displayMetadata.LinuxDRMConnector + "/edid_override")
if err != nil {
return fmt.Errorf("failed to open EDID override file for monitor '%s': %w", displayMetadata.LinuxDRMConnector, err)
}
defer drmFile.Close()
if _, err := drmFile.Write([]byte("reset")); err != nil {
return fmt.Errorf("failed to unload EDID firmware for monitor '%s': %w", displayMetadata.LinuxDRMConnector, err)
}
return nil
}

View file

@ -0,0 +1,21 @@
//go:build darwin
// +build darwin
package edidtools
import "fmt"
// Attempts to fetch the EDID firmware for any supported XR glasses device
func FetchXRGlassEDID(allowUnsupportedDevices bool) (*DisplayMetadata, error) {
return nil, fmt.Errorf("automatic fetching of EDID data is not supported on macOS")
}
// Loads custom firmware for a supported XR glass device
func LoadCustomEDIDFirmware(displayMetadata *DisplayMetadata, edidFirmware []byte) error {
return fmt.Errorf("loading custom EDID firmware is not supported on macOS")
}
// Unloads custom firmware for a supported XR glass device
func UnloadCustomEDIDFirmware(displayMetadata *DisplayMetadata) error {
return fmt.Errorf("unloading custom EDID firmware is not supported on macOS")
}

View file

@ -0,0 +1,21 @@
//go:build windows
// +build windows
package edidtools
import "fmt"
// Attempts to fetch the EDID firmware for any supported XR glasses device
func FetchXRGlassEDID(allowUnsupportedDevices bool) (*DisplayMetadata, error) {
return nil, fmt.Errorf("automatic fetching of EDID data is not supported on Windows")
}
// Loads custom firmware for a supported XR glass device
func LoadCustomEDIDFirmware(displayMetadata *DisplayMetadata, edidFirmware []byte) error {
return fmt.Errorf("loading custom EDID firmware is not supported on Windows")
}
// Unloads custom firmware for a supported XR glass device
func UnloadCustomEDIDFirmware(displayMetadata *DisplayMetadata) error {
return fmt.Errorf("unloading custom EDID firmware is not supported on Windows")
}

14
app/edidtools/quirks.go Normal file
View file

@ -0,0 +1,14 @@
package edidtools
// Vendor and devices names sourced from "https://uefi.org/uefi-pnp-export"
var QuirksRegistry = map[string]map[string]DisplayQuirks{
"MRG": {
"Air": {
MaxWidth: 1920,
MaxHeight: 1080,
MaxRefreshRate: 120,
SensorInitDelay: 10,
ZVectorDisabled: true,
},
},
}

20
app/edidtools/struct.go Normal file
View file

@ -0,0 +1,20 @@
package edidtools
type DisplayQuirks struct {
MaxWidth int
MaxHeight int
MaxRefreshRate int
SensorInitDelay int
ZVectorDisabled bool
}
type DisplayMetadata struct {
EDID []byte
DeviceVendor string
DeviceQuirks DisplayQuirks
MaxWidth int
MaxHeight int
MaxRefreshRate int
LinuxDRMCard string
LinuxDRMConnector string
}

View file

@ -1,11 +1,15 @@
package main
import (
"bufio"
"context"
"fmt"
"os"
"path"
libconfig "git.terah.dev/UnrealXR/unrealxr/app/config"
"git.terah.dev/UnrealXR/unrealxr/app/edidtools"
"git.terah.dev/UnrealXR/unrealxr/edidpatcher"
"github.com/charmbracelet/log"
"github.com/goccy/go-yaml"
"github.com/kirsle/configdir"
@ -35,7 +39,7 @@ func mainEntrypoint(context.Context, *cli.Command) error {
if err != nil {
log.Debug("Creating default config file")
err := os.WriteFile(path.Join(configDir, "config.yml"), InitialConfig, 0644)
err := os.WriteFile(path.Join(configDir, "config.yml"), libconfig.InitialConfig, 0644)
if err != nil {
return fmt.Errorf("failed to create initial config file: %w", err)
@ -49,14 +53,53 @@ func mainEntrypoint(context.Context, *cli.Command) error {
return fmt.Errorf("failed to read config file: %w", err)
}
config := &Config{}
config := &libconfig.Config{}
err = yaml.Unmarshal(configBytes, config)
if err != nil {
return fmt.Errorf("failed to parse config file: %w", err)
}
InitializePotentiallyMissingConfigValues(config)
libconfig.InitializePotentiallyMissingConfigValues(config)
log.Info("Attempting to read display EDID file and fetch metadata")
displayMetadata, err := edidtools.FetchXRGlassEDID(*config.Overrides.AllowUnsupportedDevices)
if err != nil {
return fmt.Errorf("failed to fetch EDID or get metadata: %w", err)
}
log.Info("Got EDID file and metadata")
log.Info("Patching EDID firmware to be specialized")
patchedFirmware, err := edidpatcher.PatchEDIDToBeSpecialized(displayMetadata.EDID)
if err != nil {
return fmt.Errorf("failed to patch EDID firmware: %w", err)
}
log.Info("Uploading patched EDID firmware")
err = edidtools.LoadCustomEDIDFirmware(displayMetadata, patchedFirmware)
if err != nil {
return fmt.Errorf("failed to upload patched EDID firmware: %w", err)
}
defer func() {
err := edidtools.UnloadCustomEDIDFirmware(displayMetadata)
if err != nil {
log.Errorf("Failed to unload custom EDID firmware: %s", err.Error())
}
log.Info("Please unplug and plug in your XR device to restore it back to normal settings.")
}()
fmt.Print("Press the Enter key to continue loading after you unplug and plug in your XR device.")
bufio.NewReader(os.Stdin).ReadBytes('\n') // Wait for Enter key press before continuing
log.Info("Initializing XR headset")
return nil
}
@ -90,6 +133,6 @@ func main() {
}
if err := cmd.Run(context.Background(), os.Args); err != nil {
log.Fatal(err)
log.Fatalf("Fatal error during execution: %s", err.Error())
}
}