1//go:build webkit_cgo
2
3package webkit
4
5/*
6#cgo pkg-config: gtk4
7#include <gtk/gtk.h>
8#include <gtk/gtktestutils.h>
9#include <gtk/gtkbox.h>
10#include <gtk/gtkpaned.h>
11#include <gdk/gdk.h>
12#include <glib.h>
13#include <glib-object.h>
14#include <graphene.h>
15#include <pthread.h>
16#include <stdint.h>
17#include <stdlib.h>
18
19// Logging helper for diagnostics
20#define WORKSPACE_LOG(fmt, ...) g_printerr("[workspace-cgo] " fmt "\n", ##__VA_ARGS__)
21
22// Helper functions for widget type checking
23static gboolean is_paned_widget(GtkWidget *widget) {
24 return GTK_IS_PANED(widget);
25}
26
27static gboolean is_box_widget(GtkWidget *widget) {
28 return GTK_IS_BOX(widget);
29}
30
31// Callback function for idle handling following the existing pattern
32extern gboolean goIdleCallback(uintptr_t handle);
33extern void goHoverCallback(uintptr_t handle);
34extern void goFocusEnterCallback(uintptr_t handle);
35extern void goFocusLeaveCallback(uintptr_t handle);
36extern void goTitleBarClickCallback(uintptr_t handle);
37
38// Main thread helpers implemented in thread_helpers.c
39void store_main_thread_id();
40int is_main_thread();
41int iterate_main_loop();
42
43static gboolean idle_callback_wrapper(gpointer data) {
44 return goIdleCallback((uintptr_t)data);
45}
46
47static guint add_idle_callback(uintptr_t handle) {
48 return g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, idle_callback_wrapper, (gpointer)handle, NULL);
49}
50
51static gboolean remove_idle_callback(uintptr_t handle) {
52 return g_idle_remove_by_data((gpointer)handle);
53}
54
55// Title bar click callback
56static void title_bar_click_cb(GtkGestureClick* gesture, gint n_press, gdouble x, gdouble y, gpointer user_data) {
57 (void)gesture; (void)n_press; (void)x; (void)y;
58 uintptr_t handle = (uintptr_t)user_data;
59 goTitleBarClickCallback(handle);
60}
61
62// Attach click handler to a widget (for title bars)
63static void widget_attach_click_handler(GtkWidget* widget, uintptr_t handle) {
64 if (!widget) {
65 return;
66 }
67 GtkGesture* click = gtk_gesture_click_new();
68 if (!click) {
69 return;
70 }
71 g_signal_connect_data(click, "pressed", G_CALLBACK(title_bar_click_cb), (gpointer)handle, NULL, 0);
72 gtk_widget_add_controller(widget, GTK_EVENT_CONTROLLER(click));
73}
74
75static void motion_enter_cb(GtkEventControllerMotion* controller, gdouble x, gdouble y, gpointer user_data) {
76 (void)controller; (void)x; (void)y;
77 uintptr_t handle = (uintptr_t)user_data;
78 goHoverCallback(handle);
79}
80
81static GtkEventController* widget_add_hover_controller(GtkWidget* widget, uintptr_t handle) {
82 if (!widget) {
83 return NULL;
84 }
85 GtkEventController* motion = gtk_event_controller_motion_new();
86 if (!motion) {
87 return NULL;
88 }
89 gtk_event_controller_set_propagation_phase(motion, GTK_PHASE_CAPTURE);
90 g_signal_connect_data(motion, "enter", G_CALLBACK(motion_enter_cb), (gpointer)handle, NULL, 0);
91 gtk_widget_add_controller(widget, motion);
92 return motion;
93}
94
95static void widget_remove_controller(GtkWidget* widget, GtkEventController* controller) {
96 if (!widget || !controller) {
97 return;
98 }
99 gtk_widget_remove_controller(widget, controller);
100}
101
102static gboolean widget_get_bounds(GtkWidget* widget, double* x, double* y, double* width, double* height) {
103 if (!widget || !x || !y || !width || !height) {
104 return FALSE;
105 }
106 GtkWidget* root = GTK_WIDGET(gtk_widget_get_root(widget));
107 if (!root) {
108 return FALSE;
109 }
110 graphene_rect_t rect;
111 if (!gtk_widget_compute_bounds(widget, root, &rect)) {
112 return FALSE;
113 }
114 *x = rect.origin.x;
115 *y = rect.origin.y;
116 *width = rect.size.width;
117 *height = rect.size.height;
118 return TRUE;
119}
120
121// Widget validation helper to prevent crashes from invalid widget pointers
122static gboolean is_valid_widget(gpointer widget) {
123 return widget != NULL && GTK_IS_WIDGET(widget);
124}
125
126// Forward declarations for focus controller callbacks defined later in this file
127static void focus_enter_cb(GtkEventControllerFocus* controller, gpointer user_data);
128static void focus_leave_cb(GtkEventControllerFocus* controller, gpointer user_data);
129
130typedef struct {
131 GtkEventController* controller;
132 GtkWidget* widget;
133 gboolean is_attached;
134} ControllerTracker;
135
136static GHashTable* controller_registry = NULL;
137
138static void init_controller_registry(void) {
139 if (controller_registry == NULL) {
140 controller_registry = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);
141 }
142}
143
144static void controller_on_widget_destroy(GtkWidget* widget, gpointer user_data) {
145 (void)widget;
146 ControllerTracker* tracker = (ControllerTracker*)user_data;
147 if (!tracker) {
148 return;
149 }
150 tracker->is_attached = FALSE;
151}
152
153static GtkEventController* widget_add_focus_controller(GtkWidget* widget, uintptr_t nodePtr) {
154 if (!widget) {
155 return NULL;
156 }
157
158 init_controller_registry();
159
160 GtkEventController* focus = gtk_event_controller_focus_new();
161 if (!focus) {
162 return NULL;
163 }
164
165 gtk_event_controller_set_propagation_phase(focus, GTK_PHASE_CAPTURE);
166 g_signal_connect(focus, "enter", G_CALLBACK(focus_enter_cb), (gpointer)nodePtr);
167 g_signal_connect(focus, "leave", G_CALLBACK(focus_leave_cb), (gpointer)nodePtr);
168
169 ControllerTracker* tracker = g_new0(ControllerTracker, 1);
170 tracker->controller = focus;
171 tracker->widget = widget;
172 tracker->is_attached = TRUE;
173
174 g_hash_table_insert(controller_registry, focus, tracker);
175
176 g_signal_connect(widget, "destroy", G_CALLBACK(controller_on_widget_destroy), tracker);
177
178 gtk_widget_add_controller(widget, focus);
179 return focus;
180}
181
182static void widget_remove_focus_controller(GtkWidget* widget, GtkEventController* controller) {
183 if (!controller_registry || !controller) {
184 return;
185 }
186
187 ControllerTracker* tracker = g_hash_table_lookup(controller_registry, controller);
188 if (!tracker) {
189 return;
190 }
191
192 if (!tracker->is_attached) {
193 return;
194 }
195
196 if (!GTK_IS_WIDGET(widget)) {
197 tracker->is_attached = FALSE;
198 return;
199 }
200
201 GtkWidget* owner = gtk_event_controller_get_widget(controller);
202 if (GTK_IS_WIDGET(owner) && owner == widget) {
203 gtk_widget_remove_controller(widget, controller);
204 }
205
206 tracker->is_attached = FALSE;
207
208 // Remove from registry; GTK drops the final reference when removing controller
209 g_hash_table_remove(controller_registry, controller);
210}
211
212static GtkWidget* paned_get_start_child(GtkWidget* paned) {
213 if (!paned) return NULL;
214 return gtk_paned_get_start_child(GTK_PANED(paned));
215}
216
217static GtkWidget* paned_get_end_child(GtkWidget* paned) {
218 if (!paned) return NULL;
219 return gtk_paned_get_end_child(GTK_PANED(paned));
220}
221
222static void on_widget_destroyed(GtkWidget* widget, gpointer user_data) {
223 (void)user_data;
224 guint ref_count = widget ? G_OBJECT(widget)->ref_count : 0;
225 WORKSPACE_LOG("widget destroyed: %p ref_count=%u", widget, ref_count);
226}
227
228static void hook_destroy_signal(GtkWidget* widget) {
229 if (!widget) return;
230 g_signal_connect(widget, "destroy", G_CALLBACK(on_widget_destroyed), NULL);
231}
232
233static unsigned int widget_get_ref_count(GtkWidget* widget) {
234 if (!widget) {
235 return 0;
236 }
237 return G_OBJECT(widget)->ref_count;
238}
239
240// Widget realization helpers for proper WebView reparenting
241// Force widget realization and ensure it's ready for rendering
242static void ensure_widget_realized(GtkWidget* widget) {
243 if (!widget) return;
244
245 // Make widget visible first
246 gtk_widget_set_visible(widget, TRUE);
247
248 // For WebKitWebView, we need to ensure the widget is properly sized
249 gtk_widget_set_size_request(widget, 100, 100); // Minimum size to force allocation
250
251 // Queue a redraw to force the rendering context to reinitialize
252 gtk_widget_queue_draw(widget);
253 WORKSPACE_LOG("ensure_widget_realized: widget=%p", widget);
254}
255
256static void add_css_provider(const char* css) {
257 if (!css) {
258 return;
259 }
260 GtkCssProvider* provider = gtk_css_provider_new();
261 if (!provider) {
262 return;
263 }
264 gtk_css_provider_load_from_string(provider, css);
265 GdkDisplay* display = gdk_display_get_default();
266 if (display) {
267 gtk_style_context_add_provider_for_display(display, GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
268 }
269 g_object_unref(provider);
270}
271
272static void widget_add_css_class(GtkWidget* widget, const char* name) {
273 if (!widget || !name) return;
274 gtk_widget_add_css_class(widget, name);
275}
276
277static void widget_remove_css_class(GtkWidget* widget, const char* name) {
278 if (!widget || !name) return;
279 gtk_widget_remove_css_class(widget, name);
280}
281
282// GtkBox helpers for stacked panes
283static GtkWidget* box_new(GtkOrientation orientation, int spacing) {
284 return gtk_box_new(orientation, spacing);
285}
286
287static void box_append(GtkWidget* box, GtkWidget* child) {
288 if (!box || !child) return;
289 gtk_box_append(GTK_BOX(box), child);
290}
291
292static void box_prepend(GtkWidget* box, GtkWidget* child) {
293 if (!box || !child) return;
294 gtk_box_prepend(GTK_BOX(box), child);
295}
296
297static void box_remove(GtkWidget* box, GtkWidget* child) {
298 if (!box || !child) return;
299 gtk_box_remove(GTK_BOX(box), child);
300}
301
302static void box_insert_child_after(GtkWidget* box, GtkWidget* child, GtkWidget* sibling) {
303 if (!box || !child) return;
304 gtk_box_insert_child_after(GTK_BOX(box), child, sibling);
305}
306
307// Widget visibility helpers for stacked panes
308static void widget_set_visible(GtkWidget* widget, gboolean visible) {
309 if (!widget) return;
310 gtk_widget_set_visible(widget, visible);
311}
312
313
314static gboolean widget_get_visible(GtkWidget* widget) {
315 if (!widget) return FALSE;
316 return gtk_widget_get_visible(widget);
317}
318
319static void widget_hide(GtkWidget* widget) {
320 if (!widget) return;
321 gtk_widget_set_visible(widget, FALSE);
322}
323
324static void widget_show(GtkWidget* widget) {
325 if (!widget) return;
326 gtk_widget_set_visible(widget, TRUE);
327}
328
329// workspace_gtk_test_init removed - moved to test files only
330
331// Label helpers for title bars
332static GtkWidget* label_new(const char* text) {
333 return gtk_label_new(text);
334}
335
336static void label_set_text(GtkWidget* label, const char* text) {
337 if (!label || !text) return;
338 gtk_label_set_text(GTK_LABEL(label), text);
339}
340
341static const char* label_get_text(GtkWidget* label) {
342 if (!label) return NULL;
343 return gtk_label_get_text(GTK_LABEL(label));
344}
345
346static void label_set_ellipsize(GtkWidget* label, int mode) {
347 if (!label) return;
348 gtk_label_set_ellipsize(GTK_LABEL(label), (PangoEllipsizeMode)mode);
349}
350
351static void label_set_max_width_chars(GtkWidget* label, int n_chars) {
352 if (!label) return;
353 gtk_label_set_max_width_chars(GTK_LABEL(label), n_chars);
354}
355
356// GTK4 Focus Controller helpers for focus state machine
357static void focus_enter_cb(GtkEventControllerFocus* controller, gpointer user_data) {
358 (void)controller;
359 uintptr_t nodePtr = (uintptr_t)user_data;
360 goFocusEnterCallback(nodePtr);
361}
362
363static void focus_leave_cb(GtkEventControllerFocus* controller, gpointer user_data) {
364 (void)controller;
365 uintptr_t nodePtr = (uintptr_t)user_data;
366 goFocusLeaveCallback(nodePtr);
367}
368
369// Widget allocation helper using modern GTK4 API
370static void widget_get_allocation_modern(GtkWidget* widget, int* x, int* y, int* width, int* height) {
371 if (!widget || !x || !y || !width || !height) {
372 *x = *y = *width = *height = 0;
373 return;
374 }
375
376 graphene_rect_t bounds;
377 // Compute bounds relative to the widget itself (pass widget as target)
378 if (gtk_widget_compute_bounds(widget, widget, &bounds)) {
379 // Extract integer coordinates from the graphene rect
380 *x = (int)bounds.origin.x;
381 *y = (int)bounds.origin.y;
382 *width = (int)bounds.size.width;
383 *height = (int)bounds.size.height;
384 } else {
385 // Fallback if compute_bounds fails
386 *x = *y = 0;
387 *width = gtk_widget_get_width(widget);
388 *height = gtk_widget_get_height(widget);
389 }
390}
391
392// GtkImage helpers for favicon display
393static GtkWidget* image_new(void) {
394 return gtk_image_new();
395}
396
397static GtkWidget* image_new_from_file(const char* filename) {
398 if (!filename) return NULL;
399 return gtk_image_new_from_file(filename);
400}
401
402static void image_set_from_file(GtkWidget* image, const char* filename) {
403 if (!image || !filename) return;
404 gtk_image_set_from_file(GTK_IMAGE(image), filename);
405}
406
407static void image_set_pixel_size(GtkWidget* image, int pixel_size) {
408 if (!image) return;
409 gtk_image_set_pixel_size(GTK_IMAGE(image), pixel_size);
410}
411*/
412import "C"
413
414import (
415 "log"
416 "sync"
417 "unsafe"
418)
419
420// WidgetBounds captures absolute widget bounds relative to the toplevel window.
421type WidgetBounds struct {
422 X float64
423 Y float64
424 Width float64
425 Height float64
426}
427
428// FocusCallbacks holds the enter and leave callbacks for focus events
429type FocusCallbacks struct {
430 OnEnter func()
431 OnLeave func()
432}
433
434// WidgetAllocation represents a widget's allocation (position and size)
435type WidgetAllocation struct {
436 X int
437 Y int
438 Width int
439 Height int
440}
441
442var (
443 hoverMu sync.Mutex
444 hoverCallbacks = make(map[uintptr]func())
445 hoverControllers = make(map[uintptr]uintptr)
446 nextHoverID uintptr = 1
447 // gtkTestInitOnce removed - no longer needed
448
449 // Focus controller management
450 focusMu sync.Mutex
451 focusCallbacks = make(map[uintptr]FocusCallbacks)
452 focusControllers = make(map[uintptr]uintptr)
453 nextFocusID uintptr = 1
454
455 // Title bar click management
456 titleBarMu sync.Mutex
457 titleBarCallbacks = make(map[uintptr]func())
458 nextTitleBarID uintptr = 1
459
460 mainThreadInitialized bool
461)
462
463// InitMainThread should be called once during gtk_init to store the main thread ID.
464func InitMainThread() {
465 if !mainThreadInitialized {
466 C.store_main_thread_id()
467 mainThreadInitialized = true
468 }
469}
470
471// IsMainThread returns true if the current goroutine is running on the GTK main thread.
472func IsMainThread() bool {
473 if !mainThreadInitialized {
474 return false
475 }
476 return C.is_main_thread() == 1
477}
478
479func widgetIsValid(widget uintptr) bool {
480 if widget == 0 {
481 return false
482 }
483 return C.is_valid_widget(C.gpointer(unsafe.Pointer(widget))) != 0
484}
485
486func widgetRefCount(widget uintptr) uint {
487 if widget == 0 || !widgetIsValid(widget) {
488 return 0
489 }
490 return uint(C.widget_get_ref_count((*C.GtkWidget)(unsafe.Pointer(widget))))
491}
492
493// WidgetIsValid exposes GTK_IS_WIDGET checks for diagnostics in Go.
494func WidgetIsValid(widget uintptr) bool {
495 return widgetIsValid(widget)
496}
497
498// IsPaned checks if a widget is a GtkPaned container.
499func IsPaned(widget uintptr) bool {
500 if widget == 0 {
501 return false
502 }
503 return C.is_paned_widget((*C.GtkWidget)(unsafe.Pointer(widget))) != 0
504}
505
506// IsBox checks if a widget is a GtkBox container.
507func IsBox(widget uintptr) bool {
508 if widget == 0 {
509 return false
510 }
511 return C.is_box_widget((*C.GtkWidget)(unsafe.Pointer(widget))) != 0
512}
513
514// WidgetRefCount exposes the widget reference count for debugging.
515func WidgetRefCount(widget uintptr) uint {
516 return widgetRefCount(widget)
517}
518
519// WidgetHookDestroy attaches a destroy signal handler for diagnostics (debug builds).
520func WidgetHookDestroy(widget uintptr) {
521 if widget == 0 || !widgetIsValid(widget) {
522 return
523 }
524 C.hook_destroy_signal((*C.GtkWidget)(unsafe.Pointer(widget)))
525}
526
527// AddCSSProvider installs application-level CSS rules.
528func AddCSSProvider(css string) {
529 if css == "" {
530 return
531 }
532 cstr := C.CString(css)
533 defer C.free(unsafe.Pointer(cstr))
534 C.add_css_provider(cstr)
535}
536
537// WidgetAddCSSClass attaches a CSS class to a widget.
538func WidgetAddCSSClass(widget uintptr, class string) {
539 if widget == 0 || class == "" || !widgetIsValid(widget) {
540 return
541 }
542 cstr := C.CString(class)
543 defer C.free(unsafe.Pointer(cstr))
544 C.widget_add_css_class((*C.GtkWidget)(unsafe.Pointer(widget)), cstr)
545}
546
547// WidgetRemoveCSSClass removes a CSS class from a widget.
548func WidgetRemoveCSSClass(widget uintptr, class string) {
549 if widget == 0 || class == "" || !widgetIsValid(widget) {
550 return
551 }
552 cstr := C.CString(class)
553 defer C.free(unsafe.Pointer(cstr))
554 C.widget_remove_css_class((*C.GtkWidget)(unsafe.Pointer(widget)), cstr)
555}
556
557// Orientation mirrors GtkOrientation for workspace layout helpers.
558type Orientation int
559
560const (
561 OrientationHorizontal Orientation = Orientation(C.GTK_ORIENTATION_HORIZONTAL)
562 OrientationVertical Orientation = Orientation(C.GTK_ORIENTATION_VERTICAL)
563)
564
565func goBool(b bool) C.gboolean {
566 if b {
567 return C.gboolean(1)
568 }
569 return C.gboolean(0)
570}
571
572// NewPaned constructs a GtkPaned widget for the given orientation.
573func NewPaned(orientation Orientation) uintptr {
574 paned := C.gtk_paned_new(C.GtkOrientation(orientation))
575 return uintptr(unsafe.Pointer(paned))
576}
577
578// PanedSetStartChild assigns the start child of a GtkPaned.
579func PanedSetStartChild(paned uintptr, child uintptr) {
580 if child != 0 {
581 // Check if widget already has a parent and force unparent if needed
582 parent := C.gtk_widget_get_parent((*C.GtkWidget)(unsafe.Pointer(child)))
583 if parent != nil {
584 C.gtk_widget_unparent((*C.GtkWidget)(unsafe.Pointer(child)))
585 }
586 }
587 C.gtk_paned_set_start_child((*C.GtkPaned)(unsafe.Pointer(paned)), (*C.GtkWidget)(unsafe.Pointer(child)))
588}
589
590// PanedSetEndChild assigns the end child of a GtkPaned.
591func PanedSetEndChild(paned uintptr, child uintptr) {
592 if child != 0 {
593 // Check if widget already has a parent and force unparent if needed
594 parent := C.gtk_widget_get_parent((*C.GtkWidget)(unsafe.Pointer(child)))
595 if parent != nil {
596 C.gtk_widget_unparent((*C.GtkWidget)(unsafe.Pointer(child)))
597 }
598 }
599 C.gtk_paned_set_end_child((*C.GtkPaned)(unsafe.Pointer(paned)), (*C.GtkWidget)(unsafe.Pointer(child)))
600}
601
602// PanedGetStartChild returns the start child pointer for diagnostics.
603func PanedGetStartChild(paned uintptr) uintptr {
604 if paned == 0 {
605 return 0
606 }
607 child := C.paned_get_start_child((*C.GtkWidget)(unsafe.Pointer(paned)))
608 return uintptr(unsafe.Pointer(child))
609}
610
611// PanedGetEndChild returns the end child pointer for diagnostics.
612func PanedGetEndChild(paned uintptr) uintptr {
613 if paned == 0 {
614 return 0
615 }
616 child := C.paned_get_end_child((*C.GtkWidget)(unsafe.Pointer(paned)))
617 return uintptr(unsafe.Pointer(child))
618}
619
620// PanedSetResizeStart configures whether the start child should expand when the paned is resized.
621func PanedSetResizeStart(paned uintptr, resize bool) {
622 C.gtk_paned_set_resize_start_child((*C.GtkPaned)(unsafe.Pointer(paned)), goBool(resize))
623}
624
625// PanedSetResizeEnd configures whether the end child should expand when the paned is resized.
626func PanedSetResizeEnd(paned uintptr, resize bool) {
627 C.gtk_paned_set_resize_end_child((*C.GtkPaned)(unsafe.Pointer(paned)), goBool(resize))
628}
629
630// PanedSetPosition sets the pixel position of the divider.
631func PanedSetPosition(paned uintptr, pos int) {
632 if paned == 0 {
633 return
634 }
635 C.gtk_paned_set_position((*C.GtkPaned)(unsafe.Pointer(paned)), C.int(pos))
636}
637
638// PanedGetPosition gets the current position of the divider in a GtkPaned
639func PanedGetPosition(paned uintptr) int {
640 if paned == 0 {
641 return 0
642 }
643 if !widgetIsValid(paned) {
644 return 0
645 }
646 return int(C.gtk_paned_get_position((*C.GtkPaned)(unsafe.Pointer(paned))))
647}
648
649// WidgetUnparent detaches a widget from its current parent.
650func WidgetUnparent(widget uintptr) {
651 if ok := WidgetUnparentChecked(widget); !ok {
652 log.Printf("[workspace] WidgetUnparent skipped for widget=%#x", widget)
653 }
654}
655
656// WidgetUnparentChecked safely detaches a widget from its parent if still valid.
657//
658// Return semantics:
659// - Returns false if the widget pointer is zero or not a valid GTK widget (invalid input).
660// - Returns true if the widget has no parent (already detached).
661// - Returns true if the widget was successfully unparented (parent changed or is now nil).
662// - Returns false if the widget is still valid and the parent did not change (unparent failed).
663//
664// Callers can use the return value to distinguish between invalid input, already-detached widgets, and successful unparenting.
665func WidgetUnparentChecked(widget uintptr) bool {
666 if widget == 0 {
667 log.Printf("[workspace] WidgetUnparentChecked: widget=0")
668 return false
669 }
670
671 if !widgetIsValid(widget) {
672 log.Printf("[workspace] WidgetUnparentChecked: widget=%#x invalid before unparent", widget)
673 return false
674 }
675
676 parentBefore := WidgetGetParent(widget)
677 if parentBefore == 0 {
678 log.Printf("[workspace] WidgetUnparentChecked: widget=%#x has no parent", widget)
679 return true
680 }
681
682 refBefore := widgetRefCount(widget)
683 log.Printf("[workspace] WidgetUnparentChecked: widget=%#x parent=%#x ref_before=%d", widget, parentBefore, refBefore)
684
685 C.gtk_widget_unparent((*C.GtkWidget)(unsafe.Pointer(widget)))
686
687 if !widgetIsValid(widget) {
688 log.Printf("[workspace] WidgetUnparentChecked: widget=%#x invalid after unparent", widget)
689 return true
690 }
691
692 parentAfter := WidgetGetParent(widget)
693 refAfter := widgetRefCount(widget)
694 log.Printf("[workspace] WidgetUnparentChecked: widget=%#x parent_after=%#x ref_after=%d", widget, parentAfter, refAfter)
695
696 return parentAfter == 0 || parentAfter != parentBefore
697}
698
699// WidgetSetHExpand configures horizontal expand for a widget.
700func WidgetSetHExpand(widget uintptr, expand bool) {
701 if widget == 0 {
702 return
703 }
704 log.Printf("[workspace] WidgetSetHExpand widget=%#x expand=%v", widget, expand)
705 C.gtk_widget_set_hexpand((*C.GtkWidget)(unsafe.Pointer(widget)), goBool(expand))
706}
707
708func WidgetGetParent(widget uintptr) uintptr {
709 if widget == 0 {
710 log.Printf("[workspace] WidgetGetParent: widget=0")
711 return 0
712 }
713 if !widgetIsValid(widget) {
714 log.Printf("[workspace] WidgetGetParent: widget=%#x invalid", widget)
715 return 0
716 }
717 parent := C.gtk_widget_get_parent((*C.GtkWidget)(unsafe.Pointer(widget)))
718 // Reduced logging: only debug mode shows individual widget operations
719 return uintptr(unsafe.Pointer(parent))
720}
721
722// WidgetSetVExpand configures vertical expand for a widget.
723func WidgetSetVExpand(widget uintptr, expand bool) {
724 if widget == 0 {
725 return
726 }
727 log.Printf("[workspace] WidgetSetVExpand widget=%#x expand=%v", widget, expand)
728 C.gtk_widget_set_vexpand((*C.GtkWidget)(unsafe.Pointer(widget)), goBool(expand))
729}
730
731// WidgetSetSizeRequest sets the minimum size request for a widget.
732// Use -1 for width or height to unset that dimension.
733func WidgetSetSizeRequest(widget uintptr, width, height int) {
734 if widget == 0 || !widgetIsValid(widget) {
735 return
736 }
737 log.Printf("[workspace] WidgetSetSizeRequest widget=%#x width=%d height=%d", widget, width, height)
738 C.gtk_widget_set_size_request((*C.GtkWidget)(unsafe.Pointer(widget)), C.int(width), C.int(height))
739}
740
741// WidgetResetSizeRequest clears explicit size constraints so GTK can recalculate allocation.
742func WidgetResetSizeRequest(widget uintptr) {
743 if widget == 0 || !widgetIsValid(widget) {
744 return
745 }
746 C.gtk_widget_set_size_request((*C.GtkWidget)(unsafe.Pointer(widget)), C.int(-1), C.int(-1))
747}
748
749// WidgetShow makes the widget visible.
750func WidgetShow(widget uintptr) {
751 if widget == 0 {
752 return
753 }
754 log.Printf("[workspace] WidgetShow widget=%#x", widget)
755 C.gtk_widget_set_visible((*C.GtkWidget)(unsafe.Pointer(widget)), C.gboolean(1))
756}
757
758// WidgetGrabFocus focuses the widget if possible.
759func WidgetGrabFocus(widget uintptr) {
760 if widget == 0 {
761 return
762 }
763 if !widgetIsValid(widget) {
764 log.Printf("[workspace] WidgetGrabFocus skipped invalid widget=%#x", widget)
765 return
766 }
767 // Reduced logging: focus grab logged only in consolidated focus operation
768 C.gtk_widget_grab_focus((*C.GtkWidget)(unsafe.Pointer(widget)))
769}
770
771// WidgetSetFocusChild sets or clears the focus child of a widget.
772// Pass 0 as child to clear the focus child (prevents GTK focus chain warnings during reparenting).
773func WidgetSetFocusChild(widget uintptr, child uintptr) {
774 if widget == 0 {
775 return
776 }
777 if !widgetIsValid(widget) {
778 log.Printf("[workspace] WidgetSetFocusChild skipped invalid widget=%#x", widget)
779 return
780 }
781 var childPtr *C.GtkWidget
782 if child != 0 {
783 if !widgetIsValid(child) {
784 log.Printf("[workspace] WidgetSetFocusChild skipped invalid child=%#x", child)
785 return
786 }
787 childPtr = (*C.GtkWidget)(unsafe.Pointer(child))
788 }
789 C.gtk_widget_set_focus_child((*C.GtkWidget)(unsafe.Pointer(widget)), childPtr)
790 log.Printf("[workspace] WidgetSetFocusChild widget=%#x child=%#x", widget, child)
791}
792
793// WidgetRef increments the reference count to keep the widget alive during reparenting.
794func WidgetRef(widget uintptr) bool {
795 if widget == 0 {
796 return false
797 }
798 if !widgetIsValid(widget) {
799 log.Printf("[workspace] WidgetRef skipped invalid widget=%#x", widget)
800 return false
801 }
802 before := widgetRefCount(widget)
803 log.Printf("[workspace] WidgetRef widget=%#x ref_before=%d", widget, before)
804 C.g_object_ref(C.gpointer(unsafe.Pointer(widget)))
805 after := widgetRefCount(widget)
806 log.Printf("[workspace] WidgetRef widget=%#x ref_after=%d", widget, after)
807 return true
808}
809
810// WidgetUnref releases a previously held widget reference.
811func WidgetUnref(widget uintptr) {
812 if widget == 0 {
813 return
814 }
815 if !widgetIsValid(widget) {
816 log.Printf("[workspace] WidgetUnref skipped invalid widget=%#x", widget)
817 return
818 }
819 before := widgetRefCount(widget)
820 log.Printf("[workspace] WidgetUnref widget=%#x ref_before=%d", widget, before)
821 C.g_object_unref(C.gpointer(unsafe.Pointer(widget)))
822 if widgetIsValid(widget) {
823 after := widgetRefCount(widget)
824 log.Printf("[workspace] WidgetUnref widget=%#x ref_after=%d", widget, after)
825 } else {
826 log.Printf("[workspace] WidgetUnref widget=%#x now invalid", widget)
827 }
828}
829
830// WidgetQueueAllocate queues an allocation on the widget.
831func WidgetQueueAllocate(widget uintptr) {
832 if widget == 0 {
833 return
834 }
835 log.Printf("[workspace] WidgetQueueAllocate widget=%#x", widget)
836 C.gtk_widget_queue_allocate((*C.GtkWidget)(unsafe.Pointer(widget)))
837}
838
839// WidgetQueueResize forces GTK to recalculate the widget's layout and size allocation.
840// This is useful when a widget's size requirements have changed (e.g., after adding children).
841func WidgetQueueResize(widget uintptr) {
842 if widget == 0 {
843 return
844 }
845 if !widgetIsValid(widget) {
846 log.Printf("[workspace] WidgetQueueResize skipped invalid widget=%#x", widget)
847 return
848 }
849 log.Printf("[workspace] WidgetQueueResize widget=%#x", widget)
850 C.gtk_widget_queue_resize((*C.GtkWidget)(unsafe.Pointer(widget)))
851}
852
853// WidgetRealizeInContainer ensures a widget is properly realized within a container
854func WidgetRealizeInContainer(widget uintptr) {
855 if widget == 0 {
856 return
857 }
858 // Reduced logging: widget realization logged only in consolidated focus operation
859 C.ensure_widget_realized((*C.GtkWidget)(unsafe.Pointer(widget)))
860}
861
862// WidgetAddHoverHandler registers a hover callback for the given widget and
863// returns a token that can be used to detach it later.
864func WidgetAddHoverHandler(widget uintptr, fn func()) uintptr {
865 if widget == 0 || fn == nil || !widgetIsValid(widget) {
866 return 0
867 }
868 hoverMu.Lock()
869 token := nextHoverID
870 nextHoverID++
871 hoverCallbacks[token] = fn
872 hoverMu.Unlock()
873
874 controller := C.widget_add_hover_controller((*C.GtkWidget)(unsafe.Pointer(widget)), C.uintptr_t(token))
875 if controller == nil {
876 hoverMu.Lock()
877 delete(hoverCallbacks, token)
878 hoverMu.Unlock()
879 return 0
880 }
881
882 hoverMu.Lock()
883 hoverControllers[token] = uintptr(unsafe.Pointer(controller))
884 hoverMu.Unlock()
885 return token
886}
887
888// WidgetRemoveHoverHandler removes a previously registered hover callback.
889func WidgetRemoveHoverHandler(widget uintptr, token uintptr) {
890 if token == 0 {
891 return
892 }
893
894 hoverMu.Lock()
895 controller := hoverControllers[token]
896 delete(hoverCallbacks, token)
897 delete(hoverControllers, token)
898 hoverMu.Unlock()
899
900 if controller == 0 {
901 return
902 }
903
904 if widget == 0 || !widgetIsValid(widget) {
905 return
906 }
907
908 C.widget_remove_controller((*C.GtkWidget)(unsafe.Pointer(widget)), (*C.GtkEventController)(unsafe.Pointer(controller)))
909}
910
911// WidgetAttachClickHandler attaches a click handler to any widget (for title bars)
912func WidgetAttachClickHandler(widget uintptr, fn func()) uintptr {
913 if widget == 0 || fn == nil || !widgetIsValid(widget) {
914 return 0
915 }
916 titleBarMu.Lock()
917 token := nextTitleBarID
918 nextTitleBarID++
919 titleBarCallbacks[token] = fn
920 titleBarMu.Unlock()
921
922 C.widget_attach_click_handler((*C.GtkWidget)(unsafe.Pointer(widget)), C.uintptr_t(token))
923
924 return token
925}
926
927// WidgetGetBounds returns the absolute bounds of the widget relative to the root window.
928func WidgetGetBounds(widget uintptr) (WidgetBounds, bool) {
929 if widget == 0 || !widgetIsValid(widget) {
930 return WidgetBounds{}, false
931 }
932
933 var x, y, width, height C.double
934 if C.widget_get_bounds((*C.GtkWidget)(unsafe.Pointer(widget)), (*C.double)(&x), (*C.double)(&y), (*C.double)(&width), (*C.double)(&height)) == 0 {
935 return WidgetBounds{}, false
936 }
937
938 return WidgetBounds{
939 X: float64(x),
940 Y: float64(y),
941 Width: float64(width),
942 Height: float64(height),
943 }, true
944}
945
946// Global storage for idle callbacks
947var (
948 idleCallbacks = make(map[uintptr]func() bool)
949 nextIdleID uintptr = 1
950)
951
952// IdleAdd schedules a function to be called when the main loop is idle.
953func IdleAdd(fn func() bool) uintptr {
954 if fn == nil {
955 return 0
956 }
957 id := nextIdleID
958 nextIdleID++
959 idleCallbacks[id] = fn
960 if C.add_idle_callback(C.uintptr_t(id)) == 0 {
961 delete(idleCallbacks, id)
962 return 0
963 }
964 return id
965}
966
967// IdleRemove cancels a previously scheduled idle callback.
968func IdleRemove(handle uintptr) {
969 if handle == 0 {
970 return
971 }
972 C.remove_idle_callback(C.uintptr_t(handle))
973 delete(idleCallbacks, handle)
974}
975
976// IterateMainLoop processes a single GTK main loop iteration and reports if work was handled.
977func IterateMainLoop() bool {
978 return C.iterate_main_loop() == 1
979}
980
981//export goIdleCallback
982func goIdleCallback(handle uintptr) C.gboolean {
983 if fn, ok := idleCallbacks[handle]; ok {
984 delete(idleCallbacks, handle) // Clean up after calling
985 if fn() {
986 return C.gboolean(1) // G_SOURCE_CONTINUE
987 }
988 }
989 return C.gboolean(0) // G_SOURCE_REMOVE
990}
991
992//export goHoverCallback
993func goHoverCallback(handle C.uintptr_t) {
994 token := uintptr(handle)
995 hoverMu.Lock()
996 fn := hoverCallbacks[token]
997 hoverMu.Unlock()
998 if fn != nil {
999 fn()
1000 }
1001}
1002
1003//export goTitleBarClickCallback
1004func goTitleBarClickCallback(handle C.uintptr_t) {
1005 token := uintptr(handle)
1006 titleBarMu.Lock()
1007 fn := titleBarCallbacks[token]
1008 titleBarMu.Unlock()
1009 if fn != nil {
1010 fn()
1011 }
1012}
1013
1014// GtkBox functions for stacked panes
1015
1016// NewBox creates a new GtkBox widget with the specified orientation and spacing.
1017func NewBox(orientation Orientation, spacing int) uintptr {
1018 box := C.box_new(C.GtkOrientation(orientation), C.int(spacing))
1019 return uintptr(unsafe.Pointer(box))
1020}
1021
1022// BoxAppend adds a child widget to the end of a GtkBox.
1023func BoxAppend(box uintptr, child uintptr) {
1024 if box == 0 || child == 0 {
1025 return
1026 }
1027 log.Printf("[workspace] BoxAppend box=%#x child=%#x", box, child)
1028 C.box_append((*C.GtkWidget)(unsafe.Pointer(box)), (*C.GtkWidget)(unsafe.Pointer(child)))
1029}
1030
1031// BoxPrepend adds a child widget to the beginning of a GtkBox.
1032func BoxPrepend(box uintptr, child uintptr) {
1033 if box == 0 || child == 0 {
1034 return
1035 }
1036 log.Printf("[workspace] BoxPrepend box=%#x child=%#x", box, child)
1037 C.box_prepend((*C.GtkWidget)(unsafe.Pointer(box)), (*C.GtkWidget)(unsafe.Pointer(child)))
1038}
1039
1040// BoxRemove removes a child widget from a GtkBox.
1041func BoxRemove(box uintptr, child uintptr) {
1042 if box == 0 || child == 0 {
1043 return
1044 }
1045 log.Printf("[workspace] BoxRemove box=%#x child=%#x", box, child)
1046 C.box_remove((*C.GtkWidget)(unsafe.Pointer(box)), (*C.GtkWidget)(unsafe.Pointer(child)))
1047}
1048
1049// BoxInsertChildAfter inserts a child widget after a sibling in a GtkBox.
1050func BoxInsertChildAfter(box uintptr, child uintptr, sibling uintptr) {
1051 if box == 0 || child == 0 {
1052 return
1053 }
1054 log.Printf("[workspace] BoxInsertChildAfter box=%#x child=%#x sibling=%#x", box, child, sibling)
1055 C.box_insert_child_after((*C.GtkWidget)(unsafe.Pointer(box)), (*C.GtkWidget)(unsafe.Pointer(child)), (*C.GtkWidget)(unsafe.Pointer(sibling)))
1056}
1057
1058// Widget visibility functions for stacked panes
1059
1060// WidgetSetVisible sets the visibility state of a widget.
1061func WidgetSetVisible(widget uintptr, visible bool) {
1062 if widget == 0 {
1063 return
1064 }
1065 log.Printf("[workspace] WidgetSetVisible widget=%#x visible=%v", widget, visible)
1066 C.widget_set_visible((*C.GtkWidget)(unsafe.Pointer(widget)), goBool(visible))
1067}
1068
1069// WidgetGetVisible returns the visibility state of a widget.
1070func WidgetGetVisible(widget uintptr) bool {
1071 if widget == 0 {
1072 return false
1073 }
1074 return C.widget_get_visible((*C.GtkWidget)(unsafe.Pointer(widget))) != 0
1075}
1076
1077// WidgetHide hides a widget.
1078func WidgetHide(widget uintptr) {
1079 if widget == 0 {
1080 return
1081 }
1082 log.Printf("[workspace] WidgetHide widget=%#x", widget)
1083 C.widget_hide((*C.GtkWidget)(unsafe.Pointer(widget)))
1084}
1085
1086// Label functions for title bars
1087
1088// NewLabel creates a new GtkLabel widget with the specified text.
1089func NewLabel(text string) uintptr {
1090 cstr := C.CString(text)
1091 defer C.free(unsafe.Pointer(cstr))
1092 label := C.label_new(cstr)
1093 return uintptr(unsafe.Pointer(label))
1094}
1095
1096// LabelSetText sets the text of a GtkLabel.
1097func LabelSetText(label uintptr, text string) {
1098 if label == 0 {
1099 return
1100 }
1101 cstr := C.CString(text)
1102 defer C.free(unsafe.Pointer(cstr))
1103 C.label_set_text((*C.GtkWidget)(unsafe.Pointer(label)), cstr)
1104}
1105
1106// LabelGetText returns the text of a GtkLabel.
1107func LabelGetText(label uintptr) string {
1108 if label == 0 {
1109 return ""
1110 }
1111 cstr := C.label_get_text((*C.GtkWidget)(unsafe.Pointer(label)))
1112 if cstr == nil {
1113 return ""
1114 }
1115 return C.GoString(cstr)
1116}
1117
1118// EllipsizeMode represents PangoEllipsizeMode values.
1119type EllipsizeMode int
1120
1121const (
1122 EllipsizeNone EllipsizeMode = 0 // PANGO_ELLIPSIZE_NONE
1123 EllipsizeStart EllipsizeMode = 1 // PANGO_ELLIPSIZE_START
1124 EllipsizeMiddle EllipsizeMode = 2 // PANGO_ELLIPSIZE_MIDDLE
1125 EllipsizeEnd EllipsizeMode = 3 // PANGO_ELLIPSIZE_END
1126)
1127
1128// LabelSetEllipsize sets the ellipsization mode for a GtkLabel.
1129func LabelSetEllipsize(label uintptr, mode EllipsizeMode) {
1130 if label == 0 {
1131 return
1132 }
1133 C.label_set_ellipsize((*C.GtkWidget)(unsafe.Pointer(label)), C.int(mode))
1134}
1135
1136// LabelSetMaxWidthChars sets the maximum width in characters for a GtkLabel.
1137func LabelSetMaxWidthChars(label uintptr, nChars int) {
1138 if label == 0 {
1139 return
1140 }
1141 C.label_set_max_width_chars((*C.GtkWidget)(unsafe.Pointer(label)), C.int(nChars))
1142}
1143
1144// WidgetWaitForDraw is deprecated - GTK4 handles widget operations automatically
1145// This function is kept for compatibility but does nothing in production
1146func WidgetWaitForDraw(widget uintptr) {
1147 // GTK4 documentation states: never force a redraw
1148 // Widget operations are handled automatically by the GTK4 main loop
1149 // This function is a no-op in production code
1150}
1151
1152// WidgetQueueDraw safely queues a redraw operation (GTK4 recommended approach)
1153func WidgetQueueDraw(widget uintptr) {
1154 if widget == 0 || !widgetIsValid(widget) {
1155 return
1156 }
1157 C.gtk_widget_queue_draw((*C.GtkWidget)(unsafe.Pointer(widget)))
1158}
1159
1160// WidgetHasCSSClass checks if a widget has a specific CSS class
1161func WidgetHasCSSClass(widget uintptr, class string) bool {
1162 if widget == 0 || class == "" || !widgetIsValid(widget) {
1163 return false
1164 }
1165 cstr := C.CString(class)
1166 defer C.free(unsafe.Pointer(cstr))
1167 result := C.gtk_widget_has_css_class((*C.GtkWidget)(unsafe.Pointer(widget)), cstr)
1168 return result != 0
1169}
1170
1171// Widget margin functions for creating visual borders
1172
1173// WidgetSetMargin sets all margins (top, right, bottom, left) to the same value
1174func WidgetSetMargin(widget uintptr, margin int) {
1175 if widget == 0 || !widgetIsValid(widget) {
1176 return
1177 }
1178 w := (*C.GtkWidget)(unsafe.Pointer(widget))
1179 C.gtk_widget_set_margin_top(w, C.int(margin))
1180 C.gtk_widget_set_margin_bottom(w, C.int(margin))
1181 C.gtk_widget_set_margin_start(w, C.int(margin))
1182 C.gtk_widget_set_margin_end(w, C.int(margin))
1183}
1184
1185// WidgetSetMarginTop sets the top margin
1186func WidgetSetMarginTop(widget uintptr, margin int) {
1187 if widget == 0 || !widgetIsValid(widget) {
1188 return
1189 }
1190 C.gtk_widget_set_margin_top((*C.GtkWidget)(unsafe.Pointer(widget)), C.int(margin))
1191}
1192
1193// WidgetSetMarginBottom sets the bottom margin
1194func WidgetSetMarginBottom(widget uintptr, margin int) {
1195 if widget == 0 || !widgetIsValid(widget) {
1196 return
1197 }
1198 C.gtk_widget_set_margin_bottom((*C.GtkWidget)(unsafe.Pointer(widget)), C.int(margin))
1199}
1200
1201// WidgetSetMarginStart sets the start (left in LTR) margin
1202func WidgetSetMarginStart(widget uintptr, margin int) {
1203 if widget == 0 || !widgetIsValid(widget) {
1204 return
1205 }
1206 C.gtk_widget_set_margin_start((*C.GtkWidget)(unsafe.Pointer(widget)), C.int(margin))
1207}
1208
1209// WidgetSetMarginEnd sets the end (right in LTR) margin
1210func WidgetSetMarginEnd(widget uintptr, margin int) {
1211 if widget == 0 || !widgetIsValid(widget) {
1212 return
1213 }
1214 C.gtk_widget_set_margin_end((*C.GtkWidget)(unsafe.Pointer(widget)), C.int(margin))
1215}
1216
1217// GTK4 Focus Controller functions for focus state machine
1218
1219// WidgetAddFocusController registers focus callbacks for the given widget and
1220// returns a token that can be used to detach it later.
1221func WidgetAddFocusController(widget uintptr, onEnter, onLeave func()) uintptr {
1222 if widget == 0 || !widgetIsValid(widget) {
1223 return 0
1224 }
1225
1226 focusMu.Lock()
1227 token := nextFocusID
1228 nextFocusID++
1229
1230 callbacks := FocusCallbacks{
1231 OnEnter: onEnter,
1232 OnLeave: onLeave,
1233 }
1234 focusCallbacks[token] = callbacks
1235 focusMu.Unlock()
1236
1237 controller := C.widget_add_focus_controller((*C.GtkWidget)(unsafe.Pointer(widget)), C.uintptr_t(token))
1238 if controller == nil {
1239 focusMu.Lock()
1240 delete(focusCallbacks, token)
1241 focusMu.Unlock()
1242 return 0
1243 }
1244
1245 focusMu.Lock()
1246 focusControllers[token] = uintptr(unsafe.Pointer(controller))
1247 focusMu.Unlock()
1248 return token
1249}
1250
1251// WidgetRemoveFocusController removes a previously registered focus controller.
1252func WidgetRemoveFocusController(widget uintptr, token uintptr) {
1253 if token == 0 {
1254 return
1255 }
1256
1257 focusMu.Lock()
1258 controller := focusControllers[token]
1259 delete(focusCallbacks, token)
1260 delete(focusControllers, token)
1261 focusMu.Unlock()
1262
1263 if controller == 0 {
1264 return
1265 }
1266
1267 if widget == 0 || !widgetIsValid(widget) {
1268 return
1269 }
1270
1271 C.widget_remove_focus_controller((*C.GtkWidget)(unsafe.Pointer(widget)), (*C.GtkEventController)(unsafe.Pointer(controller)))
1272}
1273
1274//export goFocusEnterCallback
1275func goFocusEnterCallback(handle C.uintptr_t) {
1276 token := uintptr(handle)
1277 focusMu.Lock()
1278 callbacks := focusCallbacks[token]
1279 focusMu.Unlock()
1280
1281 if callbacks.OnEnter != nil {
1282 callbacks.OnEnter()
1283 }
1284}
1285
1286//export goFocusLeaveCallback
1287func goFocusLeaveCallback(handle C.uintptr_t) {
1288 token := uintptr(handle)
1289 focusMu.Lock()
1290 callbacks := focusCallbacks[token]
1291 focusMu.Unlock()
1292
1293 if callbacks.OnLeave != nil {
1294 callbacks.OnLeave()
1295 }
1296}
1297
1298// WidgetGetAllocation gets the bounds (position and size) of a widget using modern GTK4 API
1299func WidgetGetAllocation(widget uintptr) WidgetAllocation {
1300 var x, y, width, height C.int
1301
1302 C.widget_get_allocation_modern(
1303 (*C.GtkWidget)(unsafe.Pointer(widget)),
1304 &x, &y, &width, &height,
1305 )
1306
1307 return WidgetAllocation{
1308 X: int(x),
1309 Y: int(y),
1310 Width: int(width),
1311 Height: int(height),
1312 }
1313}
1314
1315// GtkImage functions for favicon display
1316
1317// NewImage creates a new empty GtkImage widget.
1318func NewImage() uintptr {
1319 image := C.image_new()
1320 return uintptr(unsafe.Pointer(image))
1321}
1322
1323// ImageNewFromFile creates a new GtkImage widget from a file path.
1324func ImageNewFromFile(filename string) uintptr {
1325 if filename == "" {
1326 return 0
1327 }
1328 cstr := C.CString(filename)
1329 defer C.free(unsafe.Pointer(cstr))
1330 image := C.image_new_from_file(cstr)
1331 return uintptr(unsafe.Pointer(image))
1332}
1333
1334// ImageSetFromFile updates an existing GtkImage to display an image from a file.
1335func ImageSetFromFile(image uintptr, filename string) {
1336 if image == 0 || filename == "" {
1337 return
1338 }
1339 cstr := C.CString(filename)
1340 defer C.free(unsafe.Pointer(cstr))
1341 C.image_set_from_file((*C.GtkWidget)(unsafe.Pointer(image)), cstr)
1342}
1343
1344// ImageSetPixelSize sets the pixel size for the image (used for scaling icons).
1345func ImageSetPixelSize(image uintptr, pixelSize int) {
1346 if image == 0 {
1347 return
1348 }
1349 C.image_set_pixel_size((*C.GtkWidget)(unsafe.Pointer(image)), C.int(pixelSize))
1350}