From ba1db9e16fc2b8ec43fcc49fd824809b6f9f8a67 Mon Sep 17 00:00:00 2001 From: Stu Date: Sat, 13 Dec 2025 21:39:04 -0500 Subject: [PATCH] Fix GTK/mpv player build issues (imports, window ID, polling) --- cmd/gtkplayer/main.go | 256 +++++++++++++++++++++++++++-------------- player/mpvembed/mpv.go | 31 +++++ 2 files changed, 199 insertions(+), 88 deletions(-) diff --git a/cmd/gtkplayer/main.go b/cmd/gtkplayer/main.go index 2eb9027..ee7542c 100644 --- a/cmd/gtkplayer/main.go +++ b/cmd/gtkplayer/main.go @@ -1,23 +1,25 @@ 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/glib" "github.com/gotk3/gotk3/gtk" ) -// simple GTK + mpv embedded player demo (single pane). +type pane struct { + area *gtk.DrawingArea + mpv *mpvembed.Client + path string +} func main() { - flag.Parse() gtk.Init(nil) win, err := gtk.WindowNew(gtk.WINDOW_TOPLEVEL) @@ -25,88 +27,30 @@ func main() { log.Fatalf("window: %v", err) } win.SetTitle("VT Player (GTK/mpv)") - win.SetDefaultSize(1280, 720) + win.SetDefaultSize(1400, 800) 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) + // Two panes for compare; left/right + left := newPane() + right := newPane() - // 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) + controls := buildControls(win, left, right) + grid.Attach(controls, 0, 0, 2, 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) - } - } - }() + grid.Attach(left.area, 0, 1, 1, 1) + grid.Attach(right.area, 1, 1, 1, 1) win.Connect("destroy", func() { + if left.mpv != nil { + left.mpv.Destroy() + } + if right.mpv != nil { + right.mpv.Destroy() + } gtk.MainQuit() }) @@ -114,36 +58,172 @@ func main() { 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) +func newPane() *pane { + da, _ := gtk.DrawingAreaNew() + da.SetHExpand(true) + da.SetVExpand(true) + p := &pane{area: da} + da.Connect("realize", func() { + if p.mpv != nil { + return + } + mpv, err := mpvembed.New() + if err != nil { + log.Printf("mpv create: %v", err) + return + } + p.mpv = mpv + if w, ok := da.GetWindow(); ok && w != nil { + if xid := getWindowID(w); xid != 0 { + _ = mpv.SetWID(xid) + } + } + _ = mpv.SetOptionString("pause", "yes") + if err := mpv.Initialize(); err != nil { + log.Printf("mpv init: %v", err) + } + }) + return p +} + +func buildControls(win *gtk.Window, left, right *pane) *gtk.Box { + box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 6) + row1, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 4) + row2, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 4) + row3, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 4) + + openL, _ := gtk.ButtonNewWithLabel("Open Left") + openR, _ := gtk.ButtonNewWithLabel("Open Right") + play, _ := gtk.ButtonNewWithLabel("Play Both") + pause, _ := gtk.ButtonNewWithLabel("Pause Both") + seek0, _ := gtk.ButtonNewWithLabel("Seek 0") + stepF, _ := gtk.ButtonNewWithLabel("Step +1f") + stepB, _ := gtk.ButtonNewWithLabel("Step -1f") + info, _ := gtk.LabelNew("Meta: -") + + openL.Connect("clicked", func() { chooseAndLoad(win, left) }) + openR.Connect("clicked", func() { chooseAndLoad(win, right) }) + + play.Connect("clicked", func() { + if left.mpv != nil { + _ = left.mpv.SetPropertyBool("pause", false) + } + if right.mpv != nil { + _ = right.mpv.SetPropertyBool("pause", false) + } + }) + pause.Connect("clicked", func() { + if left.mpv != nil { + _ = left.mpv.SetPropertyBool("pause", true) + } + if right.mpv != nil { + _ = right.mpv.SetPropertyBool("pause", true) + } + }) + seek0.Connect("clicked", func() { + if left.mpv != nil { + _ = left.mpv.Command("seek", "0", "absolute", "exact") + } + if right.mpv != nil { + _ = right.mpv.Command("seek", "0", "absolute", "exact") + } + }) + stepF.Connect("clicked", func() { + if left.mpv != nil { + _ = left.mpv.Command("frame-step") + } + if right.mpv != nil { + _ = right.mpv.Command("frame-step") + } + }) + stepB.Connect("clicked", func() { + if left.mpv != nil { + _ = left.mpv.Command("frame-back-step") + } + if right.mpv != nil { + _ = right.mpv.Command("frame-back-step") + } + }) + + // Poll meta/progress + go func() { + t := time.NewTicker(500 * time.Millisecond) + defer t.Stop() + for range t.C { + text := metaSummary(left, right) + glib.IdleAdd(func() { info.SetText(text) }) + } + }() + + row1.PackStart(openL, false, false, 0) + row1.PackStart(openR, false, false, 0) + row1.PackStart(play, false, false, 0) + row1.PackStart(pause, false, false, 0) + row1.PackStart(seek0, false, false, 0) + row1.PackStart(stepB, false, false, 0) + row1.PackStart(stepF, false, false, 0) + + row2.PackStart(info, false, false, 0) + + box.PackStart(row1, false, false, 0) + box.PackStart(row2, false, false, 0) + box.PackStart(row3, false, false, 0) + return box +} + +func chooseAndLoad(win *gtk.Window, p *pane) { + dlg, _ := gtk.FileChooserDialogNewWith1Button("Open Video", win, gtk.FILE_CHOOSER_ACTION_OPEN, "Open", gtk.RESPONSE_ACCEPT) + if resp := dlg.Run(); resp == gtk.RESPONSE_ACCEPT { + filename := dlg.GetFilename() + if filename != "" { + loadIntoPane(p, filename) + } } - if d, err := mpv.GetPropertyDouble("duration"); err == nil { - *duration = d + dlg.Destroy() +} + +func loadIntoPane(p *pane, filename string) { + if p.mpv == nil { + return } - _ = mpv.SetPropertyBool("pause", false) + p.path = filename + if err := p.mpv.Command("loadfile", filename, "replace"); err != nil { + log.Printf("loadfile %s: %v", filename, err) + } + _ = p.mpv.SetPropertyBool("pause", false) +} + +func metaSummary(a, b *pane) string { + parts := func(p *pane) string { + if p == nil || p.mpv == nil || p.path == "" { + return "[empty]" + } + dur, _ := p.mpv.GetPropertyDouble("duration") + pos, _ := p.mpv.GetPropertyDouble("time-pos") + w, _ := p.mpv.GetPropertyInt64("width") + h, _ := p.mpv.GetPropertyInt64("height") + return fmt.Sprintf("%s | %dx%d | %.1f/%.1fs", filepath.Base(p.path), w, h, pos, dur) + } + return fmt.Sprintf("L: %s | R: %s", parts(a), parts(b)) } // 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 + return uint64(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 { +func gdkWindowGetXID(w *gdk.Window) uint { type xidGetter interface { GetXID() uint } if xw, ok := w.(xidGetter); ok { - return uint64(xw.GetXID()) + return xw.GetXID() } return 0 } - diff --git a/player/mpvembed/mpv.go b/player/mpvembed/mpv.go index 9b2a48c..64b78c3 100644 --- a/player/mpvembed/mpv.go +++ b/player/mpvembed/mpv.go @@ -144,3 +144,34 @@ func (c *Client) GetPropertyDouble(name string) (float64, error) { } return float64(out), nil } + +// GetPropertyInt64 gets an int64 property. +func (c *Client) GetPropertyInt64(name string) (int64, error) { + if c.handle == nil { + return 0, errors.New("mpv handle is nil") + } + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + var out C.longlong + if res := C.mpv_get_property(c.handle, cname, C.mpv_format(C.MPV_FORMAT_INT64), unsafe.Pointer(&out)); res < 0 { + return 0, fmt.Errorf("mpv_get_property %s failed: %s", name, C.GoString(C.mpv_errstr(res))) + } + return int64(out), nil +} + +// GetPropertyString gets a string property. +func (c *Client) GetPropertyString(name string) (string, error) { + if c.handle == nil { + return "", errors.New("mpv handle is nil") + } + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + var out *C.char + if res := C.mpv_get_property(c.handle, cname, C.mpv_format(C.MPV_FORMAT_STRING), unsafe.Pointer(&out)); res < 0 { + return "", fmt.Errorf("mpv_get_property %s failed: %s", name, C.GoString(C.mpv_errstr(res))) + } + if out == nil { + return "", nil + } + return C.GoString(out), nil +}