mirror of
https://github.com/Alexander-D-Karpov/netfetch.git
synced 2026-03-16 22:07:03 +03:00
409 lines
8.9 KiB
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"
|
|
}
|