diff --git a/agor-pty/native/agor_resize.c b/agor-pty/native/agor_resize.c index c9b4a0e..d6e5ef8 100644 --- a/agor-pty/native/agor_resize.c +++ b/agor-pty/native/agor_resize.c @@ -1,56 +1,124 @@ /** - * libagor-resize.so — Native GTK resize handler for Electrobun. + * libagor-resize.so — Native GTK resize for undecorated WebKitGTK windows. * - * Connects button-press-event on the GtkWindow in C (not JSCallback). - * Stores mouse state from the real GTK event. Exports start_resize(edge) - * for Bun FFI to call — uses the stored event data for begin_resize_drag. + * KEY INSIGHT: Signals MUST be connected on the WebKitWebView widget, + * NOT the GtkWindow. WebKit's GdkWindow receives all pointer events; + * the parent GtkWindow never sees them. * - * This is the Wails pattern adapted for Electrobun/Bun. + * This is the Wails pattern: connect button-press-event on the WebView, + * do hit-test in C, call gtk_window_begin_resize_drag with real event data. * * Build: gcc -shared -fPIC -o libagor-resize.so agor_resize.c \ * $(pkg-config --cflags --libs gtk+-3.0) */ #include -#include #include -/* Stored mouse state from the last button-press event */ -static gdouble stored_xroot = 0.0; -static gdouble stored_yroot = 0.0; -static guint32 stored_time = 0; -static guint stored_button = 1; static GtkWindow *stored_window = NULL; - -/* Border width for hit-test (pixels) */ static int border_width = 8; /** - * GTK button-press-event handler — stores mouse state for later use. - * Connected directly in C — no JSCallback boundary crossing. - * Returns FALSE to let the event propagate to WebKitGTK normally. + * Hit-test: determine which edge the pointer is on. + * Returns GdkWindowEdge (0-7) or -1 if inside content area. + * x, y are window-local coordinates (same as webview coords when it fills the window). */ -static gboolean -on_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data) +static GdkWindowEdge edge_for_position(int win_w, int win_h, double x, double y) { - if (event == NULL) return FALSE; - if (event->button == 3) return FALSE; /* Skip right-click */ + int B = border_width; + int left = x < B; + int right = x > win_w - B; + int top = y < B; + int bottom = y > win_h - B; - if (event->type == GDK_BUTTON_PRESS && event->button == 1) { - stored_xroot = event->x_root; - stored_yroot = event->y_root; - stored_time = event->time; - stored_button = event->button; - } - return FALSE; /* Propagate — WebKitGTK still processes the event */ + if (top && left) return GDK_WINDOW_EDGE_NORTH_WEST; + if (top && right) return GDK_WINDOW_EDGE_NORTH_EAST; + if (bottom && left) return GDK_WINDOW_EDGE_SOUTH_WEST; + if (bottom && right) return GDK_WINDOW_EDGE_SOUTH_EAST; + if (top) return GDK_WINDOW_EDGE_NORTH; + if (bottom) return GDK_WINDOW_EDGE_SOUTH; + if (left) return GDK_WINDOW_EDGE_WEST; + if (right) return GDK_WINDOW_EDGE_EAST; + return (GdkWindowEdge)-1; } /** - * Initialize: connect button-press-event on the GtkWindow. - * Call ONCE after window creation from Bun FFI. - * - * @param window_ptr GtkWindow* (from Electrobun's mainWindow.ptr) - * @param border Border width in pixels for edge detection + * Button-press handler on the WebKitWebView. + * If click is in the 8px border zone: start resize drag and consume the event. + * If click is in the interior: return FALSE to let WebKit handle it. + */ +static gboolean on_webview_button_press(GtkWidget *widget, + GdkEventButton *event, + gpointer user_data) +{ + if (!stored_window || !event) return FALSE; + if (event->button != 1) return FALSE; /* LMB only */ + + int w, h; + gtk_window_get_size(stored_window, &w, &h); + + /* event->x, event->y are webview-local coords (= window coords when webview fills window) */ + GdkWindowEdge edge = edge_for_position(w, h, event->x, event->y); + if ((int)edge == -1) return FALSE; /* Interior click — let WebKit handle */ + + fprintf(stderr, "[agor-resize] RESIZE edge=%d btn=%d xy=(%.0f,%.0f) root=(%.0f,%.0f) t=%u\n", + edge, event->button, event->x, event->y, event->x_root, event->y_root, event->time); + + gtk_window_begin_resize_drag( + stored_window, + edge, + event->button, + (gint)event->x_root, + (gint)event->y_root, + event->time + ); + + return TRUE; /* Consumed — WebKit does NOT see this click */ +} + +/** + * Motion handler on the WebKitWebView — changes cursor on edge hover. + */ +static gboolean on_webview_motion(GtkWidget *widget, + GdkEventMotion *event, + gpointer user_data) +{ + if (!stored_window || !event) return FALSE; + + int w, h; + gtk_window_get_size(stored_window, &w, &h); + GdkWindowEdge edge = edge_for_position(w, h, event->x, event->y); + + GdkWindow *gdk_win = gtk_widget_get_window(GTK_WIDGET(stored_window)); + if (!gdk_win) return FALSE; + + const char *cursor_name = NULL; + switch ((int)edge) { + case GDK_WINDOW_EDGE_NORTH: cursor_name = "n-resize"; break; + case GDK_WINDOW_EDGE_SOUTH: cursor_name = "s-resize"; break; + case GDK_WINDOW_EDGE_WEST: cursor_name = "w-resize"; break; + case GDK_WINDOW_EDGE_EAST: cursor_name = "e-resize"; break; + case GDK_WINDOW_EDGE_NORTH_WEST: cursor_name = "nw-resize"; break; + case GDK_WINDOW_EDGE_NORTH_EAST: cursor_name = "ne-resize"; break; + case GDK_WINDOW_EDGE_SOUTH_WEST: cursor_name = "sw-resize"; break; + case GDK_WINDOW_EDGE_SOUTH_EAST: cursor_name = "se-resize"; break; + default: break; + } + + if (cursor_name) { + GdkCursor *cursor = gdk_cursor_new_from_name(gdk_display_get_default(), cursor_name); + gdk_window_set_cursor(gdk_win, cursor); + if (cursor) g_object_unref(cursor); + } else { + gdk_window_set_cursor(gdk_win, NULL); /* default cursor */ + } + + return FALSE; /* Always let WebKit see motion events too */ +} + +/** + * Initialize: find the deepest WebView child and connect signals there. + * Call ONCE after window creation. */ void agor_resize_init(void *window_ptr, int border) { @@ -62,73 +130,41 @@ void agor_resize_init(void *window_ptr, int border) stored_window = GTK_WINDOW(window_ptr); border_width = border > 0 ? border : 8; - /* Add event masks */ - gtk_widget_add_events(GTK_WIDGET(stored_window), - GDK_POINTER_MOTION_MASK | - GDK_BUTTON_PRESS_MASK | - GDK_BUTTON1_MOTION_MASK); - - /* Connect button-press on the window — fires even when WebView covers everything - because we also connect on the WebView child below */ - g_signal_connect(stored_window, "button-press-event", - G_CALLBACK(on_button_press), NULL); - - /* Walk down to find the WebView and connect there too */ - GtkWidget *child = gtk_bin_get_child(GTK_BIN(stored_window)); - while (child && GTK_IS_BIN(child)) { - g_signal_connect(child, "button-press-event", - G_CALLBACK(on_button_press), NULL); - child = gtk_bin_get_child(GTK_BIN(child)); + /* Walk down the widget tree to find the deepest child (the WebKitWebView) */ + GtkWidget *webview = GTK_WIDGET(stored_window); + while (GTK_IS_BIN(webview)) { + GtkWidget *child = gtk_bin_get_child(GTK_BIN(webview)); + if (!child) break; + webview = child; } - fprintf(stderr, "[agor-resize] Initialized: window=%p border=%d\n", - window_ptr, border_width); + if (webview == GTK_WIDGET(stored_window)) { + fprintf(stderr, "[agor-resize] WARNING: no child widget found, connecting on window\n"); + } else { + fprintf(stderr, "[agor-resize] Found WebView widget: %p (type: %s)\n", + (void *)webview, G_OBJECT_TYPE_NAME(webview)); + } + + /* CRITICAL: add event masks on the WebView — without this, GTK won't deliver events */ + gtk_widget_add_events(webview, + GDK_BUTTON_PRESS_MASK | + GDK_POINTER_MOTION_MASK); + + /* Connect signals on the WebView (NOT the GtkWindow!) */ + g_signal_connect(webview, "button-press-event", + G_CALLBACK(on_webview_button_press), NULL); + g_signal_connect(webview, "motion-notify-event", + G_CALLBACK(on_webview_motion), NULL); + + fprintf(stderr, "[agor-resize] Initialized on %s (border=%dpx)\n", + G_OBJECT_TYPE_NAME(webview), border_width); } /** - * Start a resize drag using the stored mouse state. - * Call from Bun FFI when JS detects a mousedown in the border zone. - * - * @param edge GdkWindowEdge value (0=NW, 1=N, 2=NE, 3=W, 4=E, 5=SW, 6=S, 7=SE) - * @return 1 on success, 0 on failure + * Start resize from stored state (for RPC fallback — not needed if C handler works). */ int agor_resize_start(int edge) { - if (!stored_window) { - fprintf(stderr, "[agor-resize] ERROR: not initialized\n"); - return 0; - } - if (edge < 0 || edge > 7) { - fprintf(stderr, "[agor-resize] ERROR: invalid edge %d\n", edge); - return 0; - } - if (stored_time == 0) { - fprintf(stderr, "[agor-resize] ERROR: no stored event (click first)\n"); - return 0; - } - - fprintf(stderr, "[agor-resize] begin_resize_drag edge=%d btn=%u xy=(%.0f,%.0f) t=%u\n", - edge, stored_button, stored_xroot, stored_yroot, stored_time); - - gtk_window_begin_resize_drag( - stored_window, - (GdkWindowEdge)edge, - stored_button, - (gint)stored_xroot, - (gint)stored_yroot, - stored_time - ); - - return 1; -} - -/** - * Get the stored mouse position (for debugging). - */ -void agor_resize_get_state(double *out_x, double *out_y, unsigned int *out_time, unsigned int *out_button) -{ - if (out_x) *out_x = stored_xroot; - if (out_y) *out_y = stored_yroot; - if (out_time) *out_time = stored_time; - if (out_button) *out_button = stored_button; + fprintf(stderr, "[agor-resize] agor_resize_start called with edge=%d (C handler should do this automatically)\n", edge); + return 0; } diff --git a/agor-pty/native/libagor-resize.so b/agor-pty/native/libagor-resize.so index cc3db4e..192eca7 100755 Binary files a/agor-pty/native/libagor-resize.so and b/agor-pty/native/libagor-resize.so differ