v0.2.2 9100209

core.go RawHistoryFolder

1// package core implements core functionality for the generated files
2// this core lib is imported by the generated code
3package core
4
5import (
6	"bytes"
7	"fmt"
8	"os"
9	"os/exec"
10	"path/filepath"
11	"runtime"
12	"strings"
13	"sync"
14	"unsafe"
15
16	"github.com/ebitengine/purego"
17)
18
19func PuregoSafeRegister(fptr interface{}, libs []uintptr, name string) {
20	for _, lib := range libs {
21		sym, err := purego.Dlsym(lib, name)
22		if err == nil {
23			registerFuncSafe(fptr, sym)
24			return
25		}
26	}
27}
28
29// registerFuncSafe wraps purego.RegisterFunc with panic recovery for
30// functions whose signatures exceed upstream purego limits (e.g. "too
31// many stack arguments"). The function pointer stays nil and will
32// panic only if actually called.
33func registerFuncSafe(fptr interface{}, sym uintptr) {
34	defer func() { recover() }()
35	purego.RegisterFunc(fptr, sym)
36}
37
38// paths to where the shared object files should be located
39// this is unique per architecture
40// Debian/Ubuntu has it split into specific arch folder, Fedora is just /usr/lib64
41// Flatpak uses /app/lib for application libraries and runtimes don't vendor `pkg-config` as the fallback
42// see:
43// https://fedora.pkgs.org/38/fedora-x86_64/gtk4-4.10.1-1.fc38.x86_64.rpm.html
44// https://fedora.pkgs.org/38/fedora-aarch64/gtk4-4.10.1-1.fc38.aarch64.rpm.html
45// https://ubuntu.pkgs.org/23.04/ubuntu-main-amd64/libgtk-4-1_4.10.1+ds-2ubuntu1_amd64.deb.html
46// https://ubuntu.pkgs.org/23.04/ubuntu-main-arm64/libgtk-4-1_4.10.1+ds-2ubuntu1_arm64.deb.html
47// https://docs.flatpak.org/en/latest/flatpak-builder-command-reference.html (see --libdir)
48var paths = map[string][]string{
49	"amd64": {"/app/lib/", "/usr/lib/x86_64-linux-gnu/", "/usr/lib64/", "/usr/lib/"},
50	"arm64": {"/app/lib/", "/usr/lib/aarch64-linux-gnu/", "/usr/lib64/", "/usr/lib/"},
51}
52
53// names is a lookup from library names to shared object filenames
54// This is populated dynamically via SetSharedLibrary
55var names = map[string][]string{}
56
57// pkgConfNames is a lookup from library names to pkg-config library names
58// This is populated dynamically via SetPackageName
59var pkgConfNames = map[string]string{}
60
61// SetPackageName registers a pkg-config package name for a library.
62// This is used by the code generator to set package names from GIR files.
63// It won't override existing entries to preserve defaults.
64func SetPackageName(libName, pkgName string) {
65	if _, exists := pkgConfNames[libName]; !exists && pkgName != "" {
66		pkgConfNames[libName] = pkgName
67	}
68}
69
70// SetSharedLibraries registers shared library names for a library.
71// This is used by the code generator to set library names from GIR files.
72// It won't override existing entries to preserve defaults.
73func SetSharedLibraries(libName string, sharedLibs []string) {
74	if _, exists := names[libName]; !exists && len(sharedLibs) > 0 {
75		names[libName] = sharedLibs
76	}
77}
78
79// findSos tries to find all shared objects from a path and a library name
80// It does this by mapping the library name to all suitable shared object filenames and then trying some suffixes
81func findSos(path string, name string) []string {
82	sos := []string{}
83	for _, n := range names[name] {
84		suffixes := []string{"", ".0", ".1", ".2"}
85		fn := filepath.Join(path, n)
86		for _, s := range suffixes {
87			if _, err := os.Stat(fn + s); err == nil {
88				sos = append(sos, fn+s)
89			}
90		}
91	}
92	return sos
93}
94
95// findPkgConf finds all shared object files with pkg-config
96// it does this by running pkg-config --libs-only-L libname
97// and then it loops over the directories returned and finds all suitable ones
98func findPkgConf(name string) []string {
99	cmd := exec.Command("pkg-config", "--libs-only-L", pkgConfNames[name])
100	var out, outerr bytes.Buffer
101	cmd.Stdout = &out
102	cmd.Stderr = &outerr
103	err := cmd.Run()
104	if err != nil {
105		fmt.Fprintf(os.Stderr, "pkg-config, failed with: %v and stderr: %s\n", err, outerr.String())
106		return []string{}
107	}
108	outs := strings.Split(out.String(), "-L")
109	for _, v := range outs {
110		c := strings.TrimSpace(v)
111		if c == "" {
112			continue
113		}
114		g := findSos(c, name)
115		if len(g) > 0 {
116			return g
117		}
118	}
119	return []string{}
120}
121
122// GetPaths gets all shared object files from a library name
123// it does it in the following order
124// see if PUREGOTK_LIBNAME_PATH is set (full path to the lib)
125// - e.g. PUREGOTK_GTK_PATH
126// see if PUREGOTK_LIB_FOLDER is set (root folder where to look for libs)
127// go over the hardcoded paths
128// find a library name with pkg-config
129// panic if failed
130// TODO: Hardcore a library shared object with linker -X flag
131// This is useful for packaging
132func GetPaths(name string) []string {
133	// try to get from env var
134	ev := fmt.Sprintf("PUREGOTK_%s_PATH", name)
135	if v := os.Getenv(ev); v != "" {
136		return []string{v}
137	}
138
139	// Or if a general folder is set where everywhere is located, return that
140	ep := os.Getenv("PUREGOTK_LIB_FOLDER")
141	if ep != "" {
142		g := findSos(ep, name)
143		if len(g) == 0 {
144			panic(fmt.Sprintf("Could not find lib: %s, at path: %s with env: %s", name, ep, "PUREGOTK_FOLDER"))
145		}
146		return g
147	}
148
149	// fallback to lookup a path if no env var is found
150	gp, ok := paths[runtime.GOARCH]
151	if ok {
152		// try to loop over paths
153		for _, p := range gp {
154			g := findSos(p, name)
155			if len(g) > 0 {
156				return g
157			}
158
159		}
160	}
161	// last effort: pkg-config
162	g := findPkgConf(name)
163	if len(g) > 0 {
164		return g
165	}
166
167	panic(fmt.Sprintf("Path for library: %s not found. Please set the path to this library shared object file manually with env variable: %s or PUREGOTK_LIB_FOLDER. Or make sure pkg-config is setup correctly", strings.ToLower(name), ev))
168}
169
170// hasSuffix tests whether the string s ends with suffix.
171// This function was copied from purego
172func hasSuffix(s, suffix string) bool {
173	return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
174}
175
176// ByteSlice creates a pointer to a byte slice of C strings
177// This function was copied from purego
178func ByteSlice(name []string) **byte {
179	if name == nil {
180		return nil
181	}
182	res := make([]*byte, len(name)+1)
183	for i, v := range name {
184		res[i] = CString(v)
185	}
186
187	// the last element is NULL terminated for GTK
188	res[len(name)] = nil
189	return &res[0]
190}
191
192// CString converts a go string to *byte that can be passed to C code.
193// This function was copied from purego
194func CString(name string) *byte {
195	if hasSuffix(name, "\x00") {
196		return &(*(*[]byte)(unsafe.Pointer(&name)))[0]
197	}
198	b := make([]byte, len(name)+1)
199	copy(b, name)
200	return &b[0]
201}
202
203// GoStringSlice gets a string slice from a char** array
204// This function was copied from purego
205func GoStringSlice(c uintptr) []string {
206	var ret []string
207	for i := 0; ; i++ {
208		ptrAddr := c + uintptr(i)*unsafe.Sizeof(uintptr(0))
209		addr := *(*unsafe.Pointer)(unsafe.Pointer(&ptrAddr))
210		// We take the address and then dereference it to trick go vet from creating a possible misuse of unsafe.Pointer
211		ptr := *(*uintptr)(addr)
212		if ptr == 0 {
213			break
214		}
215		ret = append(ret, GoString(ptr))
216	}
217
218	return ret
219}
220
221// GoString copies a char* to a Go string.
222// This function was copied from purego
223func GoString(c uintptr) string {
224	// We take the address and then dereference it to trick go vet from creating a possible misuse of unsafe.Pointer
225	ptr := *(*unsafe.Pointer)(unsafe.Pointer(&c))
226	if ptr == nil {
227		return ""
228	}
229	var length int
230	for {
231		if *(*byte)(unsafe.Add(ptr, uintptr(length))) == '\x00' {
232			break
233		}
234		length++
235	}
236	return string(unsafe.Slice((*byte)(ptr), length))
237}
238
239var (
240	xGStrdup    func(string) uintptr
241	gstrdupOnce sync.Once
242	xGFree      func(uintptr)
243	gfreeOnce   sync.Once
244)
245
246// GStrdup allocates a C-owned copy of a Go string using g_strdup.
247// The returned pointer must be freed with g_free (typically by the callee
248// when transfer-ownership="full").
249func GStrdup(s string) uintptr {
250	gstrdupOnce.Do(func() {
251		var libs []uintptr
252		for _, libPath := range GetPaths("GLIB") {
253			lib, err := purego.Dlopen(libPath, purego.RTLD_NOW|purego.RTLD_GLOBAL)
254			if err != nil {
255				continue
256			}
257			libs = append(libs, lib)
258		}
259		PuregoSafeRegister(&xGStrdup, libs, "g_strdup")
260	})
261	return xGStrdup(s)
262}
263
264// GStrdupNullable is like GStrdup but accepts a nullable *string.
265// Returns 0 for nil, or a g_strdup'd copy for non-nil.
266func GStrdupNullable(s *string) uintptr {
267	if s == nil {
268		return 0
269	}
270	return GStrdup(*s)
271}
272
273// GFree frees memory allocated by GLib allocation APIs (for example g_strdup).
274// Passing 0 is a no-op.
275func GFree(ptr uintptr) {
276	if ptr == 0 {
277		return
278	}
279	gfreeOnce.Do(func() {
280		var libs []uintptr
281		for _, libPath := range GetPaths("GLIB") {
282			lib, err := purego.Dlopen(libPath, purego.RTLD_NOW|purego.RTLD_GLOBAL)
283			if err != nil {
284				continue
285			}
286			libs = append(libs, lib)
287		}
288		PuregoSafeRegister(&xGFree, libs, "g_free")
289	})
290	xGFree(ptr)
291}
292
293// GFreeNullable frees a nullable GLib-allocated pointer.
294func GFreeNullable(ptr uintptr) {
295	if ptr == 0 {
296		return
297	}
298	GFree(ptr)
299}
300
301// NullableStringToPtr converts a nullable Go string to a uintptr suitable for C calls.
302// Returns both the pointer and the backing byte slice. The caller MUST call
303// runtime.KeepAlive(bytes) after the C call completes to prevent GC from collecting
304// the memory before C has finished reading it.
305func NullableStringToPtr(s *string) (uintptr, []byte) {
306	if s == nil {
307		return 0, nil
308	}
309	b := append([]byte(*s), 0)
310	return uintptr(unsafe.Pointer(&b[0])), b
311}
312
313// PtrToNullableString converts a nullable char* to a Go *string (nil when NULL).
314func PtrToNullableString(ptr uintptr) *string {
315	if ptr == 0 {
316		return nil
317	}
318	str := GoString(ptr)
319	return &str
320}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 102

↑↓ moveenter openesc close