forked from Leak_Technologies/VideoTools
Fix CGO type errors and improve GTK player
- Fix render.go CGO type assignments using plain uint32 casts - Add video playlist tracking with unique IDs - Improve drag-and-drop: assign to first available pane - Add parseURIs with crash protection for drag data - Improve mpv initialization handling - Update .gitignore for build artifacts (.cache, gtkplayer binary) - Improve GDK_BACKEND handling in run script 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
bbe45c6524
commit
9d33575ada
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,6 +1,9 @@
|
||||||
videotools.log
|
videotools.log
|
||||||
.gocache/
|
.gocache/
|
||||||
.gomodcache/
|
.gomodcache/
|
||||||
|
.cache/
|
||||||
VideoTools
|
VideoTools
|
||||||
VTPlayer
|
VTPlayer
|
||||||
vt_player
|
vt_player
|
||||||
|
cmd/gtkplayer/gtkplayer
|
||||||
|
test_*.sh
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.leaktechnologies.dev/stu/VT_Player/player/mpvembed"
|
"git.leaktechnologies.dev/stu/VT_Player/player/mpvembed"
|
||||||
|
|
||||||
|
|
@ -40,10 +40,21 @@ type pane struct {
|
||||||
area *gtk.DrawingArea
|
area *gtk.DrawingArea
|
||||||
mpv *mpvembed.Client
|
mpv *mpvembed.Client
|
||||||
path string
|
path string
|
||||||
|
id int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pane) hasVideo() bool { return p.path != "" }
|
func (p *pane) hasVideo() bool { return p.path != "" }
|
||||||
|
|
||||||
|
type videoEntry struct {
|
||||||
|
id int
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
playlist []videoEntry
|
||||||
|
nextVideoID = 1
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
gtk.Init(nil)
|
gtk.Init(nil)
|
||||||
|
|
||||||
|
|
@ -70,7 +81,8 @@ func main() {
|
||||||
applyCSS()
|
applyCSS()
|
||||||
preferDark()
|
preferDark()
|
||||||
|
|
||||||
setupDragDest(left, right, win)
|
setupDragDest(left, left, right)
|
||||||
|
setupDragDest(right, left, right)
|
||||||
|
|
||||||
win.Connect("destroy", func() {
|
win.Connect("destroy", func() {
|
||||||
if left.mpv != nil {
|
if left.mpv != nil {
|
||||||
|
|
@ -92,25 +104,32 @@ func newPane() *pane {
|
||||||
da.SetVExpand(true)
|
da.SetVExpand(true)
|
||||||
p := &pane{area: da}
|
p := &pane{area: da}
|
||||||
da.Connect("realize", func() {
|
da.Connect("realize", func() {
|
||||||
if p.mpv != nil {
|
var xid uint64
|
||||||
return
|
if w, err := da.GetWindow(); err == nil && w != nil {
|
||||||
|
xid = getWindowID(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.mpv == nil {
|
||||||
mpv, err := mpvembed.New()
|
mpv, err := mpvembed.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("mpv create: %v", err)
|
log.Printf("mpv create: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.mpv = mpv
|
p.mpv = mpv
|
||||||
|
_ = p.mpv.SetOptionString("pause", "yes")
|
||||||
if w, err := da.GetWindow(); err == nil && w != nil {
|
if xid != 0 {
|
||||||
if xid := getWindowID(w); xid != 0 {
|
_ = p.mpv.SetWID(xid)
|
||||||
_ = mpv.SetWID(xid)
|
|
||||||
}
|
}
|
||||||
}
|
if err := p.mpv.Initialize(); err != nil {
|
||||||
_ = mpv.SetOptionString("pause", "yes")
|
|
||||||
if err := mpv.Initialize(); err != nil {
|
|
||||||
log.Printf("mpv init: %v", err)
|
log.Printf("mpv init: %v", err)
|
||||||
}
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// mpv already exists (created before realize); make sure WID is bound now
|
||||||
|
if xid != 0 {
|
||||||
|
_ = p.mpv.SetWID(xid)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
@ -218,6 +237,7 @@ func loadIntoPane(p *pane, filename string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.path = filename
|
p.path = filename
|
||||||
|
p.id = getOrAddVideoID(filename)
|
||||||
if err := p.mpv.Command("loadfile", filename, "replace"); err != nil {
|
if err := p.mpv.Command("loadfile", filename, "replace"); err != nil {
|
||||||
log.Printf("loadfile %s: %v", filename, err)
|
log.Printf("loadfile %s: %v", filename, err)
|
||||||
}
|
}
|
||||||
|
|
@ -233,7 +253,11 @@ func metaSummary(a, b *pane) string {
|
||||||
pos, _ := p.mpv.GetPropertyDouble("time-pos")
|
pos, _ := p.mpv.GetPropertyDouble("time-pos")
|
||||||
w, _ := p.mpv.GetPropertyInt64("width")
|
w, _ := p.mpv.GetPropertyInt64("width")
|
||||||
h, _ := p.mpv.GetPropertyInt64("height")
|
h, _ := p.mpv.GetPropertyInt64("height")
|
||||||
return fmt.Sprintf("%s | %dx%d | %.1f/%.1fs", filepath.Base(p.path), w, h, pos, dur)
|
tag := ""
|
||||||
|
if p.id > 0 {
|
||||||
|
tag = fmt.Sprintf("#%d ", p.id)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s%s | %dx%d | %.1f/%.1fs", tag, filepath.Base(p.path), w, h, pos, dur)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("L: %s | R: %s", parts(a), parts(b))
|
return fmt.Sprintf("L: %s | R: %s", parts(a), parts(b))
|
||||||
}
|
}
|
||||||
|
|
@ -273,15 +297,15 @@ func preferDark() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupDragDest(p *pane, win *gtk.Window) {
|
func setupDragDest(targetPane *pane, left, right *pane) {
|
||||||
// Accept URI drops using a target list
|
uriTarget, err := gtk.TargetEntryNew("text/uri-list", gtk.TARGET_OTHER_APP, 0)
|
||||||
target, err := gtk.TargetEntryNew("text/uri-list", gtk.TARGET_OTHER_APP, 0)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// DragDestSet requires at least one target; use the URI target.
|
targets := []gtk.TargetEntry{*uriTarget}
|
||||||
p.area.DragDestSet(gtk.DEST_DEFAULT_ALL, []gtk.TargetEntry{*target}, gdk.ACTION_COPY)
|
targetPane.area.DragDestSet(gtk.DEST_DEFAULT_ALL, targets, gdk.ACTION_COPY)
|
||||||
p.area.Connect("drag-data-received", func(_ *gtk.DrawingArea, ctx *gdk.DragContext, x, y int, data *gtk.SelectionData, info uint, t uint32) {
|
|
||||||
|
targetPane.area.Connect("drag-data-received", func(_ *gtk.DrawingArea, _ *gdk.DragContext, x, y int, data *gtk.SelectionData, _ uint, _ uint32) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
log.Printf("drag handler panic: %v", r)
|
log.Printf("drag handler panic: %v", r)
|
||||||
|
|
@ -290,24 +314,13 @@ func setupDragDest(p *pane, win *gtk.Window) {
|
||||||
if data == nil {
|
if data == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
raw := data.GetData()
|
|
||||||
if len(raw) == 0 {
|
uris := parseURIs(data)
|
||||||
// try text fallback
|
for _, u := range uris {
|
||||||
if txt := data.GetText(); txt != "" {
|
if u == "" {
|
||||||
raw = []byte(txt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(raw) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// text/uri-list: newline or CRLF separated
|
|
||||||
lines := strings.Split(string(raw), "\n")
|
|
||||||
for _, ln := range lines {
|
|
||||||
ln = strings.TrimSpace(ln)
|
|
||||||
if ln == "" {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
assignPathToPane(ln, p, nil)
|
assignPathToPane(u, left, right)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -370,3 +383,56 @@ func uriToPath(u string) string {
|
||||||
}
|
}
|
||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getOrAddVideoID(path string) int {
|
||||||
|
if path == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
for _, e := range playlist {
|
||||||
|
if e.path == path {
|
||||||
|
return e.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
id := nextVideoID
|
||||||
|
nextVideoID++
|
||||||
|
playlist = append(playlist, videoEntry{id: id, path: path})
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseURIs tries to extract URIs from SelectionData while avoiding crashes on bad payloads.
|
||||||
|
func parseURIs(data *gtk.SelectionData) []string {
|
||||||
|
if data == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// try safe path using raw bytes first
|
||||||
|
raw := data.GetData()
|
||||||
|
if len(raw) == 0 {
|
||||||
|
if txt := data.GetText(); txt != "" {
|
||||||
|
raw = []byte(txt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(raw) > 0 {
|
||||||
|
var out []string
|
||||||
|
for _, ln := range strings.Split(string(raw), "\n") {
|
||||||
|
ln = strings.TrimSpace(ln)
|
||||||
|
if ln != "" {
|
||||||
|
out = append(out, ln)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(out) > 0 {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback to GetURIs; guard with recover because upstream may panic on nil C arrays
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Printf("GetURIs panic: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if uris := data.GetURIs(); len(uris) > 0 {
|
||||||
|
return uris
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,10 @@ func NewRenderContext(c *Client, params []RenderParam) (*RenderContext, error) {
|
||||||
}
|
}
|
||||||
cparams := make([]C.mpv_render_param, len(params)+1)
|
cparams := make([]C.mpv_render_param, len(params)+1)
|
||||||
for i, p := range params {
|
for i, p := range params {
|
||||||
cparams[i]._type = C.int(p.Type)
|
cparams[i]._type = uint32(p.Type)
|
||||||
cparams[i].data = p.Data
|
cparams[i].data = p.Data
|
||||||
}
|
}
|
||||||
cparams[len(params)]._type = C.MPV_RENDER_PARAM_INVALID
|
cparams[len(params)]._type = uint32(C.MPV_RENDER_PARAM_INVALID)
|
||||||
|
|
||||||
var rctx *C.mpv_render_context
|
var rctx *C.mpv_render_context
|
||||||
if res := C.mpv_render_context_create(&rctx, c.handle, &cparams[0]); res < 0 {
|
if res := C.mpv_render_context_create(&rctx, c.handle, &cparams[0]); res < 0 {
|
||||||
|
|
@ -70,10 +70,10 @@ func (r *RenderContext) Render(params []RenderParam) error {
|
||||||
}
|
}
|
||||||
cparams := make([]C.mpv_render_param, len(params)+1)
|
cparams := make([]C.mpv_render_param, len(params)+1)
|
||||||
for i, p := range params {
|
for i, p := range params {
|
||||||
cparams[i]._type = C.int(p.Type)
|
cparams[i]._type = uint32(p.Type)
|
||||||
cparams[i].data = p.Data
|
cparams[i].data = p.Data
|
||||||
}
|
}
|
||||||
cparams[len(params)]._type = C.MPV_RENDER_PARAM_INVALID
|
cparams[len(params)]._type = uint32(C.MPV_RENDER_PARAM_INVALID)
|
||||||
|
|
||||||
if res := C.mpv_render_context_render(r.ctx, &cparams[0]); res < 0 {
|
if res := C.mpv_render_context_render(r.ctx, &cparams[0]); res < 0 {
|
||||||
return fmt.Errorf("mpv_render_context_render failed: %s", C.GoString(C.mpv_errstr_render(res)))
|
return fmt.Errorf("mpv_render_context_render failed: %s", C.GoString(C.mpv_errstr_render(res)))
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,10 @@ if [ -d "$GTK_ENTRY" ]; then
|
||||||
echo "🚀 Starting VT Player (GTK/mpv)..."
|
echo "🚀 Starting VT Player (GTK/mpv)..."
|
||||||
echo "════════════════════════════════════════════════════════════════"
|
echo "════════════════════════════════════════════════════════════════"
|
||||||
echo ""
|
echo ""
|
||||||
|
# Prefer an explicit backend only if the user hasn’t set one; default to X11 (works under XWayland).
|
||||||
|
if [ -z "$GDK_BACKEND" ]; then
|
||||||
export GDK_BACKEND=x11
|
export GDK_BACKEND=x11
|
||||||
|
fi
|
||||||
export GOCACHE="$PROJECT_ROOT/.cache/go-build"
|
export GOCACHE="$PROJECT_ROOT/.cache/go-build"
|
||||||
export GOMODCACHE="$PROJECT_ROOT/.cache/go-mod"
|
export GOMODCACHE="$PROJECT_ROOT/.cache/go-mod"
|
||||||
GOCACHE="$GOCACHE" GOMODCACHE="$GOMODCACHE" \
|
GOCACHE="$GOCACHE" GOMODCACHE="$GOMODCACHE" \
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user