This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch main
in repository ego.

View the commit online.

commit 4fe3919819fb43e1c4976378cc5d904a6059e427
Author: [email protected] <[email protected]>
AuthorDate: Wed Apr 1 10:35:25 2026 -0600

    feat(efl): add Eina_Content wrapper for MIME-typed data
    
    Add efl.Content type wrapping Eina_Content with NewContent,
    Data, Type, Free, and Ptr. Used for drag-and-drop and
    clipboard operations.
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
 efl/content.go      | 128 ++++++++++++++++++++++++++++++++++++++++++++++++
 efl/content_test.go | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 266 insertions(+)

diff --git a/efl/content.go b/efl/content.go
new file mode 100644
index 0000000..0237bad
--- /dev/null
+++ b/efl/content.go
@@ -0,0 +1,128 @@
+package efl
+
+/*
+#cgo pkg-config: efl
+
+#include <Eina.h>
+#include <stdlib.h>
+
+// _ego_content_new constructs an Eina_Content from a raw pointer and length,
+// avoiding the need for cgo to pass Eina_Slice by value directly.
+static Eina_Content *_ego_content_new(const void *data, size_t len, const char *type) {
+    Eina_Slice s;
+    s.len = len;
+    s.mem = data;
+    return eina_content_new(s, type);
+}
+
+// _ego_content_data_mem returns the memory pointer from the data slice of c.
+static const void *_ego_content_data_mem(Eina_Content *c) {
+    Eina_Slice s = eina_content_data_get(c);
+    return s.mem;
+}
+
+// _ego_content_data_len returns the byte length of the data slice of c.
+static size_t _ego_content_data_len(Eina_Content *c) {
+    Eina_Slice s = eina_content_data_get(c);
+    return s.len;
+}
+*/
+import "C"
+
+import "unsafe"
+
+// Content wraps an Eina_Content pointer, which pairs a byte slice with an IANA
+// MIME type string. It is the data carrier for drag-and-drop and clipboard
+// operations in EFL.
+//
+// When owned is true the Go wrapper is responsible for calling eina_content_free
+// when Free is invoked. Use NewContent to create an owned Content; use
+// WrapContent when the lifetime is managed externally by EFL.
+type Content struct {
+	ptr   *C.Eina_Content
+	owned bool
+}
+
+// NewContent allocates a new Eina_Content that copies data and records mimeType.
+// The caller owns the returned Content and must call Free when done.
+// Returns nil when the underlying C call fails.
+func NewContent(data []byte, mimeType string) *Content {
+	if len(data) == 0 {
+		// eina_content_new requires a non-empty slice for text; allow callers to
+		// distinguish a genuine empty payload by passing a single zero byte when
+		// the slice is truly empty. Here we pass through what was given and let
+		// the C library decide — callers must not pass nil data.
+		return nil
+	}
+
+	ctype := C.CString(mimeType)
+	defer C.free(unsafe.Pointer(ctype))
+
+	ptr := C._ego_content_new(unsafe.Pointer(&data[0]), C.size_t(len(data)), ctype)
+	if ptr == nil {
+		return nil
+	}
+	return &Content{ptr: ptr, owned: true}
+}
+
+// WrapContent creates a non-owning Content wrapper around an existing
+// Eina_Content pointer. The pointer must remain valid for the lifetime of the
+// returned Content. Free on a non-owned Content is a no-op.
+func WrapContent(ptr unsafe.Pointer) *Content {
+	if ptr == nil {
+		return nil
+	}
+	return &Content{ptr: (*C.Eina_Content)(ptr), owned: false}
+}
+
+// Ptr returns the underlying Eina_Content pointer as an unsafe.Pointer.
+// It is nil-safe: calling Ptr on a nil *Content returns nil.
+func (c *Content) Ptr() unsafe.Pointer {
+	if c == nil {
+		return nil
+	}
+	return unsafe.Pointer(c.ptr)
+}
+
+// IsNil reports whether the Content has no underlying Eina_Content pointer.
+// It is nil-safe: calling IsNil on a nil *Content returns true.
+func (c *Content) IsNil() bool {
+	if c == nil {
+		return true
+	}
+	return c.ptr == nil
+}
+
+// Type returns the MIME type string stored in the content.
+// Returns an empty string when the Content is nil or has no pointer.
+func (c *Content) Type() string {
+	if c == nil || c.ptr == nil {
+		return ""
+	}
+	return C.GoString(C.eina_content_type_get(c.ptr))
+}
+
+// Data returns a copy of the raw bytes stored in the content.
+// Returns nil when the Content is nil or has no pointer.
+func (c *Content) Data() []byte {
+	if c == nil || c.ptr == nil {
+		return nil
+	}
+	mem := C._ego_content_data_mem(c.ptr)
+	length := C._ego_content_data_len(c.ptr)
+	if mem == nil || length == 0 {
+		return nil
+	}
+	return C.GoBytes(unsafe.Pointer(mem), C.int(length))
+}
+
+// Free releases the underlying Eina_Content when this wrapper owns it.
+// Calling Free on a nil or non-owned Content is a no-op. After Free the
+// pointer is set to nil so subsequent calls are safe.
+func (c *Content) Free() {
+	if c == nil || c.ptr == nil || !c.owned {
+		return
+	}
+	C.eina_content_free(c.ptr)
+	c.ptr = nil
+}
diff --git a/efl/content_test.go b/efl/content_test.go
new file mode 100644
index 0000000..525d1eb
--- /dev/null
+++ b/efl/content_test.go
@@ -0,0 +1,138 @@
+package efl_test
+
+import (
+	"bytes"
+	"testing"
+
+	"git.enlightenment.org/cedric/ego/efl"
+)
+
+// TestContentTextRoundTrip verifies that text data survives a NewContent/Data
+// round-trip intact. The EFL documentation requires text slices to be
+// NUL-terminated, so the payload includes the terminator.
+func TestContentTextRoundTrip(t *testing.T) {
+	text := "hello, eina content\x00"
+	payload := []byte(text)
+
+	c := efl.NewContent(payload, "text/plain")
+	if c == nil {
+		t.Fatal("NewContent returned nil")
+	}
+	defer c.Free()
+
+	got := c.Data()
+	if !bytes.Equal(got, payload) {
+		t.Errorf("Data round-trip mismatch: got %q, want %q", got, payload)
+	}
+}
+
+// TestContentTypeGetter verifies that the MIME type stored at construction is
+// returned unchanged by Type.
+func TestContentTypeGetter(t *testing.T) {
+	tests := []struct {
+		name     string
+		mimeType string
+		payload  []byte
+	}{
+		{"text/plain", "text/plain", []byte("data\x00")},
+		{"application/octet-stream", "application/octet-stream", []byte{0x01, 0x02, 0x03}},
+		{"image/png", "image/png", []byte{0x89, 0x50, 0x4e, 0x47}},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			c := efl.NewContent(tt.payload, tt.mimeType)
+			if c == nil {
+				t.Fatalf("NewContent returned nil for mime type %q", tt.mimeType)
+			}
+			defer c.Free()
+
+			if got := c.Type(); got != tt.mimeType {
+				t.Errorf("Type() = %q, want %q", got, tt.mimeType)
+			}
+		})
+	}
+}
+
+// TestContentFreeIdempotent verifies that calling Free twice does not panic or
+// corrupt state — the second Free must be a no-op.
+func TestContentFreeIdempotent(t *testing.T) {
+	c := efl.NewContent([]byte("safe\x00"), "text/plain")
+	if c == nil {
+		t.Fatal("NewContent returned nil")
+	}
+	c.Free()
+	// Second call must not crash or double-free.
+	c.Free()
+}
+
+// TestContentIsNil verifies the IsNil predicate across the three cases: a nil
+// pointer, a zero-value Content, and a live Content.
+func TestContentIsNil(t *testing.T) {
+	t.Run("nil pointer", func(t *testing.T) {
+		var c *efl.Content
+		if !c.IsNil() {
+			t.Error("IsNil on nil *Content should return true")
+		}
+	})
+
+	t.Run("after Free", func(t *testing.T) {
+		c := efl.NewContent([]byte("x\x00"), "text/plain")
+		if c == nil {
+			t.Fatal("NewContent returned nil")
+		}
+		c.Free()
+		if !c.IsNil() {
+			t.Error("IsNil after Free should return true")
+		}
+	})
+
+	t.Run("live content", func(t *testing.T) {
+		c := efl.NewContent([]byte("live\x00"), "text/plain")
+		if c == nil {
+			t.Fatal("NewContent returned nil")
+		}
+		defer c.Free()
+		if c.IsNil() {
+			t.Error("IsNil on live Content should return false")
+		}
+	})
+}
+
+// TestContentBinaryData verifies that arbitrary binary payloads (including
+// interior NUL bytes) survive the round-trip without truncation.
+func TestContentBinaryData(t *testing.T) {
+	payload := []byte{0x00, 0x01, 0x02, 0x7f, 0x80, 0xfe, 0xff}
+
+	c := efl.NewContent(payload, "application/octet-stream")
+	if c == nil {
+		t.Fatal("NewContent returned nil")
+	}
+	defer c.Free()
+
+	got := c.Data()
+	if !bytes.Equal(got, payload) {
+		t.Errorf("binary round-trip mismatch: got %v, want %v", got, payload)
+	}
+}
+
+// TestContentNilSafety verifies that methods on a nil *Content do not panic and
+// return appropriate zero values.
+func TestContentNilSafety(t *testing.T) {
+	var c *efl.Content
+
+	if p := c.Ptr(); p != nil {
+		t.Errorf("Ptr on nil Content: got %v, want nil", p)
+	}
+	if got := c.Type(); got != "" {
+		t.Errorf("Type on nil Content: got %q, want empty", got)
+	}
+	if got := c.Data(); got != nil {
+		t.Errorf("Data on nil Content: got %v, want nil", got)
+	}
+	// Free and IsNil must not panic.
+	c.Free()
+	if !c.IsNil() {
+		t.Error("IsNil on nil Content: want true")
+	}
+}

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to