Skip to main content
Glama
desktop.go12.2 kB
package device import ( "fmt" "os" "os/exec" "path/filepath" "strconv" "strings" ) // DesktopManager manages desktop devices (implements DeviceManager) type DesktopManager struct { osType string } // GetIdentifiers returns device identifiers for desktop devices func (m *DesktopManager) GetIdentifiers(deviceID string) (Identifiers, error) { // Desktop devices don't have Android ID // Try to get regId from local file regId, _ := m.GetRegId(deviceID) // non-fatal return Identifiers{ SerialNo: deviceID, // Use deviceID as serialno for desktop AndroidID: nil, // Desktop devices don't have Android ID RegId: regId, }, nil } // GetDisplayResolution returns the primary/built-in display resolution for desktop devices func (m *DesktopManager) GetDisplayResolution(deviceID string) (int, int, error) { switch m.osType { case "macos": return getMacOSDisplayResolution() case "linux": return getLinuxDisplayResolution() case "windows": return getWindowsDisplayResolution() default: return 0, 0, fmt.Errorf("unsupported OS type: %s", m.osType) } } // getMacOSDisplayResolution gets the primary display resolution on macOS func getMacOSDisplayResolution() (int, int, error) { // Use system_profiler to get display info // Priority: 1. Built-in display, 2. Main Display, 3. First display cmd := exec.Command("system_profiler", "SPDisplaysDataType") output, err := cmd.Output() if err != nil { return 0, 0, err } lines := strings.Split(string(output), "\n") var builtInResolution string var mainDisplayResolution string var firstResolution string // Track current display context type displayContext struct { isBuiltIn bool isMainDisplay bool resolution string } var currentDisplay displayContext displays := []displayContext{} // Parse output line by line // Display sections start with "Displays:" and each display name is indented with spaces/tabs inDisplaysSection := false for _, line := range lines { trimmed := strings.TrimSpace(line) // Detect when we enter the Displays section if strings.Contains(trimmed, "Displays:") { inDisplaysSection = true continue } // Only process lines within Displays section if !inDisplaysSection { continue } // Detect new display section (indented line ending with colon, like " Mi 27 NU:") // This is a display name line (has indentation and ends with colon, but no other content) if strings.HasSuffix(trimmed, ":") && (strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t")) { // Check if this looks like a display name (simple name, not a key-value pair) // Display names typically don't have spaces before the colon in the key part namePart := strings.TrimSuffix(trimmed, ":") if !strings.Contains(namePart, ":") && namePart != "" { // Save previous display if it has resolution if currentDisplay.resolution != "" { displays = append(displays, currentDisplay) } // Start new display context currentDisplay = displayContext{} continue } } // Check if current display is built-in if strings.Contains(trimmed, "Display Type: Built-in") || strings.Contains(trimmed, "Built-in: Yes") { currentDisplay.isBuiltIn = true } // Check if current display is main display if strings.Contains(trimmed, "Main Display: Yes") { currentDisplay.isMainDisplay = true } // Extract resolution from Resolution line if strings.Contains(trimmed, "Resolution:") { parts := strings.Split(trimmed, ":") if len(parts) >= 2 { res := strings.TrimSpace(parts[1]) // Extract resolution (e.g., "3840 x 2160", "3456 x 2234 Retina") // Remove text after resolution like "(2160p/4K UHD 1 - Ultra High Definition)" or "Retina" resParts := strings.Fields(res) // Find the first two numeric fields (skip "x" if present) var widthStr, heightStr string for _, part := range resParts { // Skip non-numeric parts like "x", "Retina", etc. if _, err := strconv.Atoi(part); err == nil { if widthStr == "" { widthStr = part } else if heightStr == "" { heightStr = part break // Found both width and height } } } if widthStr != "" && heightStr != "" { currentDisplay.resolution = widthStr + "x" + heightStr } } } } // Save last display if it has resolution if currentDisplay.resolution != "" { displays = append(displays, currentDisplay) } // Find resolutions based on priority for _, display := range displays { if display.resolution == "" { continue } if firstResolution == "" { firstResolution = display.resolution } if display.isBuiltIn && builtInResolution == "" { builtInResolution = display.resolution } if display.isMainDisplay && mainDisplayResolution == "" { mainDisplayResolution = display.resolution } } // Use priority: Built-in > Main Display > First var resolution string if builtInResolution != "" { resolution = builtInResolution } else if mainDisplayResolution != "" { resolution = mainDisplayResolution } else if firstResolution != "" { resolution = firstResolution } if resolution == "" { return 0, 0, fmt.Errorf("could not determine display resolution") } // Parse resolution string (e.g., "3840x2160") parts := strings.Split(resolution, "x") if len(parts) != 2 { return 0, 0, fmt.Errorf("invalid resolution format: %s", resolution) } width, err := strconv.Atoi(strings.TrimSpace(parts[0])) if err != nil { return 0, 0, fmt.Errorf("invalid width: %s", parts[0]) } height, err := strconv.Atoi(strings.TrimSpace(parts[1])) if err != nil { return 0, 0, fmt.Errorf("invalid height: %s", parts[1]) } return width, height, nil } // getLinuxDisplayResolution gets the primary display resolution on Linux func getLinuxDisplayResolution() (int, int, error) { // Try xrandr first (most common) cmd := exec.Command("xrandr") output, err := cmd.Output() if err == nil { lines := strings.Split(string(output), "\n") for _, line := range lines { // Look for line with "*" (current resolution) or "connected primary" if strings.Contains(line, "connected primary") || strings.Contains(line, "*") { // Parse resolution from line like " 1920x1080 60.00*+" fields := strings.Fields(line) for _, field := range fields { if strings.Contains(field, "x") { parts := strings.Split(field, "x") if len(parts) == 2 { width, err1 := strconv.Atoi(parts[0]) height, err2 := strconv.Atoi(strings.TrimSuffix(parts[1], "*+")) if err1 == nil && err2 == nil { return width, height, nil } } } } } } } // Fallback: try wayland-info or other methods return 0, 0, fmt.Errorf("could not determine display resolution") } // getWindowsDisplayResolution gets the primary display resolution on Windows func getWindowsDisplayResolution() (int, int, error) { // Use PowerShell to get display resolution cmd := exec.Command("powershell", "-Command", "Get-WmiObject -Class Win32_VideoController | Select-Object -First 1 | Select-Object -ExpandProperty CurrentHorizontalResolution, CurrentVerticalResolution") output, err := cmd.Output() if err != nil { return 0, 0, err } lines := strings.Split(strings.TrimSpace(string(output)), "\n") if len(lines) >= 2 { width, err1 := strconv.Atoi(strings.TrimSpace(lines[0])) height, err2 := strconv.Atoi(strings.TrimSpace(lines[1])) if err1 == nil && err2 == nil { return width, height, nil } } return 0, 0, fmt.Errorf("could not determine display resolution") } // SetRegId writes regId to local file for desktop devices func (m *DesktopManager) SetRegId(deviceID string, regId string) error { return writeLocalRegId(regId) } // GetRegId reads regId from local file for desktop devices func (m *DesktopManager) GetRegId(deviceID string) (string, error) { return readLocalRegId() } // GetDevices returns empty list for desktop devices (not applicable) func (m *DesktopManager) GetDevices() ([]DeviceInfo, error) { return []DeviceInfo{}, nil } // GetOSVersion returns the OS version for desktop devices func (m *DesktopManager) GetOSVersion(deviceID string) (string, error) { switch m.osType { case "macos": cmd := exec.Command("sw_vers", "-productVersion") output, err := cmd.Output() if err != nil { return "", err } return strings.TrimSpace(string(output)), nil case "linux": // Try to get distribution version from /etc/os-release cmd := exec.Command("cat", "/etc/os-release") output, err := cmd.Output() if err == nil { lines := strings.Split(string(output), "\n") for _, line := range lines { if strings.HasPrefix(line, "VERSION_ID=") { version := strings.Trim(strings.TrimPrefix(line, "VERSION_ID="), "\"") return version, nil } } } return "", fmt.Errorf("failed to get Linux version") case "windows": // Use PowerShell to get Windows version cmd := exec.Command("powershell", "-Command", "(Get-CimInstance Win32_OperatingSystem).Version") output, err := cmd.Output() if err != nil { return "", err } return strings.TrimSpace(string(output)), nil default: return "", fmt.Errorf("unsupported OS type: %s", m.osType) } } // GetMemory returns the total memory in GB for desktop devices func (m *DesktopManager) GetMemory(deviceID string) (string, error) { switch m.osType { case "macos": // Try system_profiler first cmd := exec.Command("system_profiler", "SPHardwareDataType") output, err := cmd.Output() if err == nil { lines := strings.Split(string(output), "\n") for _, line := range lines { if strings.Contains(line, "Memory:") { parts := strings.Split(line, ":") if len(parts) >= 2 { return strings.TrimSpace(parts[1]), nil } } } } // Fallback: calculate from sysctl cmd2 := exec.Command("sysctl", "-n", "hw.memsize") output2, err2 := cmd2.Output() if err2 == nil { memBytes, err := strconv.ParseInt(strings.TrimSpace(string(output2)), 10, 64) if err == nil { memGB := float64(memBytes) / (1024 * 1024 * 1024) return fmt.Sprintf("%.0f GB", memGB), nil } } return "", fmt.Errorf("failed to get macOS memory") case "linux": // Read from /proc/meminfo cmd := exec.Command("cat", "/proc/meminfo") output, err := cmd.Output() if err != nil { return "", err } lines := strings.Split(string(output), "\n") for _, line := range lines { if strings.HasPrefix(line, "MemTotal:") { fields := strings.Fields(line) if len(fields) >= 2 { memKB, err := strconv.ParseInt(fields[1], 10, 64) if err == nil { memGB := float64(memKB) / (1024 * 1024) return fmt.Sprintf("%.0f GB", memGB), nil } } } } return "", fmt.Errorf("failed to parse memory information") case "windows": // Use PowerShell to get total memory cmd := exec.Command("powershell", "-Command", "$totalRAM = (Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory; [math]::Round($totalRAM / 1GB, 0)") output, err := cmd.Output() if err != nil { return "", err } memGB := strings.TrimSpace(string(output)) return fmt.Sprintf("%s GB", memGB), nil default: return "", fmt.Errorf("unsupported OS type: %s", m.osType) } } // getLocalRegIdPath returns the file path for storing the reg_id on this machine. func getLocalRegIdPath() (string, error) { home, err := os.UserHomeDir() if err != nil { return "", err } dir := filepath.Join(home, ".gbox") return filepath.Join(dir, "reg_id"), nil } // readLocalRegId reads reg_id from ~/.gbox/reg_id if exists. func readLocalRegId() (string, error) { path, err := getLocalRegIdPath() if err != nil { return "", err } data, err := os.ReadFile(path) if err != nil { return "", err } // Trim trailing spaces/newlines s := strings.TrimSpace(string(data)) return s, nil } // writeLocalRegId writes reg_id into ~/.gbox/reg_id, creating directory if needed. func writeLocalRegId(regId string) error { path, err := getLocalRegIdPath() if err != nil { return err } if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { return err } return os.WriteFile(path, []byte(regId+"\n"), 0o600) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/babelcloud/gru-sandbox'

If you have feedback or need assistance with the MCP directory API, please join our Discord server