1package cmd
2
3import (
4 "fmt"
5
6 "github.com/spf13/cobra"
7
8 "github.com/bnema/dumber/internal/application/usecase"
9 "github.com/bnema/dumber/internal/cli/styles"
10 "github.com/bnema/dumber/internal/infrastructure/deps"
11 "github.com/bnema/dumber/internal/infrastructure/media"
12)
13
14var (
15 doctorOnlyRuntime bool
16 doctorOnlyMedia bool
17)
18
19var doctorCmd = &cobra.Command{
20 Use: "doctor",
21 Short: "Check runtime requirements and diagnose issues",
22 Long: `Doctor checks system prerequisites for running the GUI browser.
23
24By default it runs both:
25- Runtime checks (GTK4 + WebKitGTK 6.0)
26- Media checks (GStreamer/VA-API)
27
28Use flags to run only one category.
29
30Examples:
31 dumber doctor
32 dumber doctor --runtime
33 dumber doctor --media`,
34 RunE: runDoctor,
35}
36
37func init() {
38 rootCmd.AddCommand(doctorCmd)
39 doctorCmd.Flags().BoolVar(&doctorOnlyRuntime, "runtime", false, "Only run runtime checks (GTK4/WebKitGTK)")
40 doctorCmd.Flags().BoolVar(&doctorOnlyMedia, "media", false, "Only run media checks (GStreamer/VA-API)")
41}
42func runDoctor(_ *cobra.Command, _ []string) error {
43 app := GetApp()
44 if app == nil {
45 return fmt.Errorf("app not initialized")
46 }
47
48 if doctorOnlyRuntime && doctorOnlyMedia {
49 return fmt.Errorf("--runtime and --media are mutually exclusive")
50 }
51
52 runRuntime := !doctorOnlyMedia
53 runMedia := !doctorOnlyRuntime
54
55 report := styles.DoctorReport{OverallOK: true}
56
57 runtimeOK := true
58 if runRuntime {
59 probe := deps.NewPkgConfigProbe()
60 runtimeUC := usecase.NewCheckRuntimeDependenciesUseCase(probe)
61 runtimeOut, err := runtimeUC.Execute(app.Ctx(), usecase.CheckRuntimeDependenciesInput{
62 Prefix: app.Config.Runtime.Prefix,
63 })
64 if err != nil {
65 return err
66 }
67
68 runtimeOK = runtimeOut.OK
69 report.Runtime = styles.DoctorRuntimeReport{
70 Prefix: runtimeOut.Prefix,
71 OK: runtimeOut.OK,
72 Checks: make([]styles.DoctorRuntimeCheck, 0, len(runtimeOut.Checks)),
73 }
74
75 for _, c := range runtimeOut.Checks {
76 report.Runtime.Checks = append(report.Runtime.Checks, styles.DoctorRuntimeCheck{
77 Name: c.DisplayName,
78 PkgConfigName: c.PkgConfigName,
79 Installed: c.Installed,
80 Version: c.Version,
81 RequiredVersion: c.RequiredVersion,
82 OK: c.MeetsRequirement,
83 Error: c.Error,
84 })
85 }
86 }
87
88 mediaOK := true
89 if runMedia {
90 adapter := media.New()
91 mediaUC := usecase.NewRunMediaDiagnosticsUseCase(adapter)
92 mediaOut, err := mediaUC.Execute(app.Ctx(), usecase.RunMediaDiagnosticsInput{})
93 if err != nil {
94 return err
95 }
96
97 mediaOK = mediaOut.GStreamerAvailable
98 report.Media = &styles.DoctorMediaReport{
99 GStreamerAvailable: mediaOut.GStreamerAvailable,
100 HWAccelAvailable: mediaOut.HWAccelAvailable,
101 AV1HWAvailable: mediaOut.AV1HWAvailable,
102 HasVAPlugin: mediaOut.HasVAPlugin,
103 HasVAAPIPlugin: mediaOut.HasVAAPIPlugin,
104 HasNVCodecPlugin: mediaOut.HasNVCodecPlugin,
105 AV1Decoders: mediaOut.AV1Decoders,
106 H264Decoders: mediaOut.H264Decoders,
107 H265Decoders: mediaOut.H265Decoders,
108 VP9Decoders: mediaOut.VP9Decoders,
109 VAAPIAvailable: mediaOut.VAAPIAvailable,
110 VAAPIDriver: mediaOut.VAAPIDriver,
111 VAAPIVersion: mediaOut.VAAPIVersion,
112 Warnings: mediaOut.Warnings,
113 }
114 }
115
116 report.OverallOK = runtimeOK && mediaOK
117
118 renderer := styles.NewDoctorRenderer(app.Theme)
119 fmt.Println(renderer.Render(report))
120
121 if runRuntime && !runtimeOK {
122 return fmt.Errorf("runtime requirements not met")
123 }
124 if runMedia && !mediaOK {
125 return fmt.Errorf("media requirements not met")
126 }
127
128 return nil
129}