From a7874dddd81b4f7cbf932d11a21a5ffafed6acf9 Mon Sep 17 00:00:00 2001 From: Dmitry Sergeev Date: Mon, 24 Aug 2026 23:07:23 +0300 Subject: [PATCH] CLI args parse basic --- cmd/mxl-player/main.go | 133 ++++++++++++++++++++++++++--------------- 1 file changed, 85 insertions(+), 48 deletions(-) diff --git a/cmd/mxl-player/main.go b/cmd/mxl-player/main.go index d804cf0..ab2bd8d 100644 --- a/cmd/mxl-player/main.go +++ b/cmd/mxl-player/main.go @@ -3,12 +3,13 @@ package main import ( "context" "errors" - "flag" "fmt" + "io" "log" "mxl-player/internal/renderer" "mxl-player/internal/sdl" "mxl-player/internal/source" + "os" "runtime" "time" "unsafe" @@ -25,7 +26,7 @@ const ( WIN_HEIGHT int32 = 720 ) -type CliArgs struct { +type appArgs struct { ShowHelp bool Domain string VideoFlowId string @@ -40,7 +41,7 @@ type CliArgs struct { func printCliHelp(fs *pflag.FlagSet) { fmt.Printf("%s %s\n", APP_NAME, APP_VER) - fmt.Println("Usage: mxl-player -d (-v | -a ) [-f] [-h]...") + fmt.Println("Usage: mxl-player -d (-v | -a ) [-f] [-h]") fmt.Println(" [-g ] [-p ] [--verbose]") fmt.Println(" or: mxl-player [--list-playback] [--list-gpu]") fmt.Println(" or: mxl-player (and set everything in GUI)") @@ -48,50 +49,62 @@ func printCliHelp(fs *pflag.FlagSet) { fs.PrintDefaults() } +func printUsage(w io.Writer) { + fmt.Fprintln(w, "Usage: mxl-player -d (-v | -a ) [-f] [-h]") + fmt.Fprintln(w, "Try 'mxl-player -h' for more information.") +} + +func checkMXLargs(args appArgs) { + if args.Domain == "" { + fmt.Fprintln(os.Stderr, "You should provide valid MXL domain and UUID of at least one flow") + printUsage(os.Stderr) + os.Exit(2) + } + + fi, err := os.Stat(args.Domain) + if err != nil || !fi.IsDir() { + fmt.Fprintln(os.Stderr, "Invalid MXL domain:", args.Domain) + fmt.Fprintln(os.Stderr, "Domain must be a directory in tmpfs") + printUsage(os.Stderr) + os.Exit(2) + } + if args.VideoFlowId == "" && args.AudioFlowId == "" { + fmt.Fprintln(os.Stderr, "You must provide at least 1 MXL flow UUID") + printUsage(os.Stderr) + os.Exit(2) + } +} + func main() { - // CLI args TODO: - // help + sample of good usage - // domain (-d, --domain) - // video flow UUID (-v, --video) - // audio flow UUID (-a, --audio) - // list-audio for list audio playback devices (-la, --list-playback) - // list-video fpr list video playback devices (-lg, --list-gpu) - // gpu-id for GPU selection (-p, --playback-id) - // playback-id for playback id selection (-g, --gpu-id) - // verbose flag, which turn on logs (-vb,--verbose) - // // video: 5fbec3b1-1b0f-417d-9059-8b94a47197ed // audio: 5fbec3b1-1b0f-417d-9059-8b94a47197ec - - var args CliArgs - flagSet := pflag.NewFlagSet(APP_NAME, pflag.ExitOnError) + var args appArgs + flagSet := pflag.NewFlagSet(APP_NAME, pflag.ContinueOnError) + flagSet.Usage = func() { printUsage(os.Stderr) } flagSet.BoolVarP(&args.ShowHelp, "help", "h", false, "Show help message and exit") flagSet.StringVarP(&args.Domain, "domain", "d", "", "MXL domain directory") flagSet.StringVarP(&args.VideoFlowId, "video", "v", "", "Video flow UUID") flagSet.StringVarP(&args.AudioFlowId, "audio", "a", "", "Audio flow UUID") flagSet.BoolVarP(&args.IsFullscreen, "fullscreen", "f", false, "Run app in fullscreen mode") - flagSet.Uint32VarP(&args.GpuId, "gpu-id", "g", 0, "GPU id") + flagSet.Uint32VarP(&args.GpuId, "gpu-id", "g", 0, "GPU id [TODO]") flagSet.Uint32VarP(&args.PlaybackId, "playback-id", "p", 0, "Playback audio device id") - flagSet.BoolVar(&args.IsVerbose, "verbose", false, "Verbose output") + flagSet.BoolVar(&args.IsVerbose, "verbose", false, "Verbose output [TODO]") flagSet.BoolVar(&args.ListAudio, "list-playback", false, "List audio playback devices and exit") - flagSet.BoolVar(&args.ListGPU, "list-gpu", false, "List GPU's and exit") - pflag.Parse() + flagSet.BoolVar(&args.ListGPU, "list-gpu", false, "List GPUs and exit") - if args.Domain == "" || args.ShowHelp { - // TODO. Fix after polishing GUI - // Cause required/optional will be changed + if err := flagSet.Parse(os.Args[1:]); err != nil { + fmt.Fprintln(os.Stderr, err) + printUsage(os.Stderr) + os.Exit(2) + } + if args.ShowHelp { printCliHelp(flagSet) + return } - return - - var videoFlowId, audioFlowId string - mxlDomain := flag.String("d", "/dev/shm/mxl", "MXL domain") - mxlVideoFlowID := flag.String("v", videoFlowId, "MXL video flow UUID") - mxlAudioFlowID := flag.String("a", audioFlowId, "MXL audio flow UUID") - flag.Parse() - if *mxlVideoFlowID == "" && *mxlAudioFlowID == "" { - log.Fatal("need at least -v or -a ") + if !args.ListAudio && !args.ListGPU { + checkMXLargs(args) } + // end of cli args parse runtime.LockOSThread() if err := sdl.Load(); err != nil { @@ -102,6 +115,21 @@ func main() { return } + sdlAudioDevice := sdl.AudioDeviceDefaultPlayback + if args.PlaybackId != 0 { + sdlAudioDevice = args.PlaybackId + } + // List audio playback devices and exit + if args.ListAudio { + devs := sdl.GetAudioPlaybackDevices() + fmt.Println("Available playback audio devices") + fmt.Println("id name") + for _, d := range devs { + fmt.Println(d.ID, d.Name) + } + return + } + windowHandler := sdl.CreateWindow(fmt.Sprintf("%s %s", APP_NAME, APP_VER), WIN_WIDTH, WIN_HEIGHT, sdl.WindowVulkan|sdl.WindowResizable) if windowHandler == 0 { sdl.Quit() @@ -144,9 +172,15 @@ func main() { if err != nil || len(devices) == 0 { panic("No Vulkan devices") } - for _, pd := range devices { - info := pd.Info() - fmt.Printf("%s (%s)\n", info.Name, info.Type) + // List GPU's and exit + if args.ListGPU { + fmt.Println("Available Vulkan physical devices:") + fmt.Println("id name (type)") + for i, pd := range devices { + info := pd.Info() + fmt.Printf("%2d %s (%s)\n", i, info.Name, info.Type) + } + return } vkPhysDevice := devices[0] @@ -194,8 +228,8 @@ func main() { } switch { - case *mxlVideoFlowID != "" && *mxlAudioFlowID != "": - syncSrc, err = source.OpenSync(*mxlDomain, *mxlVideoFlowID, *mxlAudioFlowID) + case args.VideoFlowId != "" && args.AudioFlowId != "": + syncSrc, err = source.OpenSync(args.Domain, args.VideoFlowId, args.AudioFlowId) if err != nil { log.Fatalf("sync source: %v", err) } @@ -204,7 +238,7 @@ func main() { if audioBatch == 0 { audioBatch = 1 } - audioStream = sdl.OpenAudioDeviceStream(sdl.AudioDeviceDefaultPlayback, sdl.AudioSpec{ + audioStream = sdl.OpenAudioDeviceStream(sdlAudioDevice, sdl.AudioSpec{ Format: sdl.AudioF32, Channels: int32(aChans), Freq: int32(syncSrc.AudioRate().Num / syncSrc.AudioRate().Den), @@ -215,16 +249,16 @@ func main() { sdl.ResumeAudioStreamDevice(audioStream) fmt.Printf("sync: video %dx%d audio %dch batch=%d\n", syncSrc.Width(), syncSrc.Height(), aChans, audioBatch) + case args.VideoFlowId != "": - case *mxlVideoFlowID != "": - videoSrc, err = source.Open(*mxlDomain, *mxlVideoFlowID) + videoSrc, err = source.Open(args.Domain, args.VideoFlowId) if err != nil { log.Fatalf("source: %v", err) } fmt.Printf("video: %dx%d stride=%d\n", videoSrc.Width(), videoSrc.Height(), videoSrc.Stride()) default: - audioSrc, err = source.OpenAudio(*mxlDomain, *mxlAudioFlowID) + audioSrc, err = source.OpenAudio(args.Domain, args.AudioFlowId) if err != nil { log.Fatalf("audio source: %v", err) } @@ -233,7 +267,7 @@ func main() { if audioBatch == 0 { audioBatch = 1 } - audioStream = sdl.OpenAudioDeviceStream(sdl.AudioDeviceDefaultPlayback, sdl.AudioSpec{ + audioStream = sdl.OpenAudioDeviceStream(sdlAudioDevice, sdl.AudioSpec{ Format: sdl.AudioF32, Channels: int32(aChans), Freq: int32(audioSrc.Rate().Num / audioSrc.Rate().Den), @@ -261,7 +295,7 @@ func main() { } var r *renderer.Renderer - if *mxlVideoFlowID != "" { + if args.VideoFlowId != "" { var w, h, stride uint32 if syncSrc != nil { w, h, stride = syncSrc.Width(), syncSrc.Height(), syncSrc.Stride() @@ -309,7 +343,7 @@ func main() { default: } if syncSrc != nil { - s, e := source.OpenSync(*mxlDomain, *mxlVideoFlowID, *mxlAudioFlowID) + s, e := source.OpenSync(args.Domain, args.VideoFlowId, args.AudioFlowId) if e == nil { if r != nil { newSize := vk.DeviceSize(s.Stride()) * vk.DeviceSize(s.Height()) @@ -324,7 +358,7 @@ func main() { } log.Printf("source: reopen retry: %v", e) } else if videoSrc != nil { - s, e := source.Open(*mxlDomain, *mxlVideoFlowID) + s, e := source.Open(args.Domain, args.VideoFlowId) if e == nil { if r != nil { newSize := vk.DeviceSize(s.Stride()) * vk.DeviceSize(s.Height()) @@ -339,7 +373,7 @@ func main() { } log.Printf("source: reopen retry: %v", e) } else if audioSrc != nil { - s, e := source.OpenAudio(*mxlDomain, *mxlAudioFlowID) + s, e := source.OpenAudio(args.Domain, args.AudioFlowId) if e == nil { audioSrc = s return nil @@ -477,7 +511,10 @@ func main() { running := true resized := false granted := false - fullscreen := false + fullscreen := args.IsFullscreen + if fullscreen { + sdl.SetWindowFullscreen(windowHandler, true) + } var ( lastIndex uint64 dropped uint64