netfetch/internal/collector/resolution.go

409 lines
8.9 KiB
Go

package collector
import (
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
)
func (c *Collector) collectResolution() {
c.mutex.Lock()
defer c.mutex.Unlock()
switch runtime.GOOS {
case "linux":
c.info.Resolution = getResolutionLinux()
case "darwin":
c.info.Resolution = getResolutionDarwin()
case "windows":
c.info.Resolution = getResolutionWindows()
case "freebsd", "openbsd", "netbsd":
c.info.Resolution = getResolutionBSD()
default:
c.info.Resolution = "Unknown"
}
}
func getResolutionLinux() string {
if os.Getenv("WAYLAND_DISPLAY") != "" {
if res := getResolutionWayland(); res != "" {
return res
}
}
if os.Getenv("DISPLAY") != "" {
if res := getResolutionX11(); res != "" {
return res
}
}
if res := getResolutionDRM(); res != "" {
return res
}
if res := getResolutionFramebuffer(); res != "" {
return res
}
return "Unknown"
}
func getResolutionWayland() string {
if os.Getenv("HYPRLAND_INSTANCE_SIGNATURE") != "" {
out, err := exec.Command("hyprctl", "monitors", "-j").Output()
if err == nil {
return parseHyprlandMonitors(string(out))
}
}
if os.Getenv("SWAYSOCK") != "" {
out, err := exec.Command("swaymsg", "-t", "get_outputs").Output()
if err == nil {
return parseSwayOutputs(string(out))
}
}
out, err := exec.Command("wlr-randr").Output()
if err == nil {
return parseWlrRandr(string(out))
}
if gnomeRes := getGnomeDisplayConfig(); gnomeRes != "" {
return gnomeRes
}
return ""
}
func parseHyprlandMonitors(output string) string {
var resolutions []string
re := regexp.MustCompile(`"width":\s*(\d+).*?"height":\s*(\d+)`)
matches := re.FindAllStringSubmatch(output, -1)
for _, match := range matches {
if len(match) >= 3 {
res := match[1] + "x" + match[2]
resolutions = append(resolutions, res)
}
}
if len(resolutions) > 0 {
return strings.Join(resolutions, ", ")
}
return ""
}
func parseSwayOutputs(output string) string {
var resolutions []string
currentRe := regexp.MustCompile(`"current_mode":\s*\{[^}]*"width":\s*(\d+)[^}]*"height":\s*(\d+)`)
matches := currentRe.FindAllStringSubmatch(output, -1)
for _, match := range matches {
if len(match) >= 3 {
res := match[1] + "x" + match[2]
resolutions = append(resolutions, res)
}
}
if len(resolutions) > 0 {
return strings.Join(resolutions, ", ")
}
return ""
}
func parseWlrRandr(output string) string {
var resolutions []string
re := regexp.MustCompile(`(\d+x\d+)\s+px,.*current`)
lines := strings.Split(output, "\n")
for _, line := range lines {
if matches := re.FindStringSubmatch(line); len(matches) >= 2 {
resolutions = append(resolutions, matches[1])
}
}
if len(resolutions) > 0 {
return strings.Join(resolutions, ", ")
}
re2 := regexp.MustCompile(`current\s+(\d+x\d+)`)
for _, line := range lines {
if matches := re2.FindStringSubmatch(line); len(matches) >= 2 {
resolutions = append(resolutions, matches[1])
}
}
if len(resolutions) > 0 {
return strings.Join(resolutions, ", ")
}
return ""
}
func getGnomeDisplayConfig() string {
out, err := exec.Command("gdbus", "call", "--session",
"--dest", "org.gnome.Mutter.DisplayConfig",
"--object-path", "/org/gnome/Mutter/DisplayConfig",
"--method", "org.gnome.Mutter.DisplayConfig.GetCurrentState").Output()
if err != nil {
return ""
}
var resolutions []string
re := regexp.MustCompile(`\(uint32 (\d+), uint32 (\d+),`)
matches := re.FindAllStringSubmatch(string(out), -1)
for _, match := range matches {
if len(match) >= 3 {
width := match[1]
height := match[2]
if width != "0" && height != "0" {
res := width + "x" + height
found := false
for _, r := range resolutions {
if r == res {
found = true
break
}
}
if !found {
resolutions = append(resolutions, res)
}
}
}
}
if len(resolutions) > 0 {
return strings.Join(resolutions, ", ")
}
return ""
}
func getResolutionX11() string {
display := os.Getenv("DISPLAY")
if display == "" {
os.Setenv("DISPLAY", ":0")
}
out, err := exec.Command("xrandr", "--current").Output()
if err != nil {
out, err = exec.Command("xrandr").Output()
if err != nil {
return ""
}
}
return parseXrandr(string(out))
}
func parseXrandr(output string) string {
var resolutions []string
lines := strings.Split(output, "\n")
connectedRe := regexp.MustCompile(`^(\S+)\s+connected\s+(?:primary\s+)?(\d+x\d+)`)
for _, line := range lines {
if matches := connectedRe.FindStringSubmatch(line); len(matches) >= 3 {
resolutions = append(resolutions, matches[2])
}
}
if len(resolutions) > 0 {
return strings.Join(resolutions, ", ")
}
modeRe := regexp.MustCompile(`^\s+(\d+x\d+)\s+.*\*`)
for _, line := range lines {
if matches := modeRe.FindStringSubmatch(line); len(matches) >= 2 {
resolutions = append(resolutions, matches[1])
}
}
if len(resolutions) > 0 {
return strings.Join(resolutions, ", ")
}
return ""
}
func getResolutionDRM() string {
var resolutions []string
connectorDirs, err := filepath.Glob("/sys/class/drm/card*-*")
if err != nil {
return ""
}
for _, connectorDir := range connectorDirs {
statusPath := filepath.Join(connectorDir, "status")
status, err := os.ReadFile(statusPath)
if err != nil || strings.TrimSpace(string(status)) != "connected" {
continue
}
modesPath := filepath.Join(connectorDir, "modes")
modes, err := os.ReadFile(modesPath)
if err != nil {
continue
}
modeLines := strings.Split(strings.TrimSpace(string(modes)), "\n")
if len(modeLines) > 0 && modeLines[0] != "" {
resolutions = append(resolutions, modeLines[0])
}
}
if len(resolutions) > 0 {
return strings.Join(resolutions, ", ")
}
return ""
}
func getResolutionFramebuffer() string {
fbPath := "/sys/class/graphics/fb0/virtual_size"
data, err := os.ReadFile(fbPath)
if err != nil {
return ""
}
size := strings.TrimSpace(string(data))
size = strings.Replace(size, ",", "x", 1)
if strings.Contains(size, "x") {
return size
}
return ""
}
func getResolutionDarwin() string {
out, err := exec.Command("system_profiler", "SPDisplaysDataType", "-json").Output()
if err == nil {
return parseDarwinDisplaysJSON(string(out))
}
out, err = exec.Command("system_profiler", "SPDisplaysDataType").Output()
if err != nil {
return "Unknown"
}
return parseDarwinDisplays(string(out))
}
func parseDarwinDisplaysJSON(output string) string {
var resolutions []string
resRe := regexp.MustCompile(`"_spdisplays_resolution":\s*"(\d+\s*x\s*\d+)`)
matches := resRe.FindAllStringSubmatch(output, -1)
for _, match := range matches {
if len(match) >= 2 {
res := strings.ReplaceAll(match[1], " ", "")
resolutions = append(resolutions, res)
}
}
if len(resolutions) > 0 {
return strings.Join(resolutions, ", ")
}
return ""
}
func parseDarwinDisplays(output string) string {
var resolutions []string
re := regexp.MustCompile(`Resolution:\s*(\d+\s*x\s*\d+)`)
matches := re.FindAllStringSubmatch(output, -1)
for _, match := range matches {
if len(match) >= 2 {
res := strings.ReplaceAll(match[1], " ", "")
resolutions = append(resolutions, res)
}
}
if len(resolutions) > 0 {
return strings.Join(resolutions, ", ")
}
return "Unknown"
}
func getResolutionWindows() string {
out, err := exec.Command("wmic", "path", "Win32_VideoController", "get",
"CurrentHorizontalResolution,CurrentVerticalResolution", "/format:csv").Output()
if err != nil {
return "Unknown"
}
var resolutions []string
lines := strings.Split(string(out), "\n")
for _, line := range lines {
fields := strings.Split(strings.TrimSpace(line), ",")
if len(fields) >= 3 {
width := strings.TrimSpace(fields[1])
height := strings.TrimSpace(fields[2])
if width != "" && height != "" && width != "CurrentHorizontalResolution" {
resolutions = append(resolutions, width+"x"+height)
}
}
}
if len(resolutions) > 0 {
return strings.Join(resolutions, ", ")
}
out, err = exec.Command("powershell", "-Command",
"Get-WmiObject Win32_VideoController | Select-Object CurrentHorizontalResolution,CurrentVerticalResolution | Format-List").Output()
if err != nil {
return "Unknown"
}
var width, height string
pLines := strings.Split(string(out), "\n")
for _, line := range pLines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "CurrentHorizontalResolution") {
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
width = strings.TrimSpace(parts[1])
}
} else if strings.HasPrefix(line, "CurrentVerticalResolution") {
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
height = strings.TrimSpace(parts[1])
}
}
}
if width != "" && height != "" {
return width + "x" + height
}
return "Unknown"
}
func getResolutionBSD() string {
if os.Getenv("DISPLAY") != "" {
out, err := exec.Command("xrandr", "--current").Output()
if err == nil {
return parseXrandr(string(out))
}
}
if res := getResolutionDRM(); res != "" {
return res
}
return "Unknown"
}