package main import ( "flag" "fmt" "log" "os" "path/filepath" "time" "git.leaktechnologies.dev/stu/VT_Player/player/mpvembed" "github.com/gotk3/gotk3/gdk" "github.com/gotk3/gotk3/gtk" ) // simple GTK + mpv embedded player demo (single pane). func main() { flag.Parse() gtk.Init(nil) win, err := gtk.WindowNew(gtk.WINDOW_TOPLEVEL) if err != nil { log.Fatalf("window: %v", err) } win.SetTitle("VT Player (GTK/mpv)") win.SetDefaultSize(1280, 720) grid, _ := gtk.GridNew() grid.SetColumnHomogeneous(true) grid.SetRowHomogeneous(false) win.Add(grid) // Drawing area for mpv da, _ := gtk.DrawingAreaNew() da.SetHexpand(true) da.SetVexpand(true) grid.Attach(da, 0, 1, 4, 1) // Controls openBtn, _ := gtk.ButtonNewWithLabel("Open...") playBtn, _ := gtk.ButtonNewWithLabel("Play") pauseBtn, _ := gtk.ButtonNewWithLabel("Pause") seekStartBtn, _ := gtk.ButtonNewWithLabel("<< Start") grid.Attach(openBtn, 0, 0, 1, 1) grid.Attach(playBtn, 1, 0, 1, 1) grid.Attach(pauseBtn, 2, 0, 1, 1) grid.Attach(seekStartBtn, 3, 0, 1, 1) mpv, err := mpvembed.New() if err != nil { log.Fatalf("mpv: %v", err) } defer mpv.Destroy() var duration float64 da.Connect("realize", func() { w := da.GetWindow() if w == nil { log.Println("no window yet") return } // Bind native window ID if xid := getWindowID(w); xid != 0 { _ = mpv.SetWID(xid) } // Set options and init _ = mpv.SetOptionString("pause", "yes") if err := mpv.Initialize(); err != nil { log.Fatalf("mpv init: %v", err) } }) openBtn.Connect("clicked", func() { dlg, _ := gtk.FileChooserDialogNewWith1Button("Open Video", win, gtk.FILE_CHOOSER_ACTION_OPEN, "Open", gtk.RESPONSE_ACCEPT) if resp := dlg.Run(); resp == int(gtk.RESPONSE_ACCEPT) { filename := dlg.GetFilename() if filename != "" { loadFile(mpv, filename, &duration) } } dlg.Destroy() }) playBtn.Connect("clicked", func() { _ = mpv.SetPropertyBool("pause", false) }) pauseBtn.Connect("clicked", func() { _ = mpv.SetPropertyBool("pause", true) }) seekStartBtn.Connect("clicked", func() { _ = mpv.Command("seek", "0", "absolute", "exact") }) // Progress poll go func() { t := time.NewTicker(250 * time.Millisecond) defer t.Stop() for range t.C { pos, err := mpv.GetPropertyDouble("time-pos") if err == nil && duration > 0 { fmt.Printf("\r%.1f / %.1f", pos, duration) } } }() win.Connect("destroy", func() { gtk.MainQuit() }) win.ShowAll() gtk.Main() } func loadFile(mpv *mpvembed.Client, path string, duration *float64) { base := filepath.Base(path) fmt.Printf("\nLoading: %s\n", base) if err := mpv.Command("loadfile", path, "replace"); err != nil { log.Printf("loadfile: %v", err) } if d, err := mpv.GetPropertyDouble("duration"); err == nil { *duration = d } _ = mpv.SetPropertyBool("pause", false) } // getWindowID returns the native window handle (XID on X11, HWND on Windows). func getWindowID(w *gdk.Window) uint64 { // X11 if xid := gdkWindowGetXID(w); xid != 0 { return xid } // TODO: add Windows handle if needed. return 0 } // gdkWindowGetXID extracts the XID from a GDK window when running on X11. func gdkWindowGetXID(w *gdk.Window) uint64 { type xidGetter interface { GetXID() uint } if xw, ok := w.(xidGetter); ok { return uint64(xw.GetXID()) } return 0 }