From 683013d704ba5051ee4e6c662d9008a20fee347e Mon Sep 17 00:00:00 2001 From: Hibryda Date: Wed, 25 Mar 2026 17:40:16 +0100 Subject: [PATCH] fix(electrobun): connect resize signals on WebKitWebView, not GtkWindow KEY FIX: WebKit's GdkWindow receives all pointer events. Connecting button-press-event on the GtkWindow never fires. Must connect on the WebKitWebView widget itself (the Wails pattern). C library now: - Walks widget tree to find deepest child (WebKitWebView) - Connects button-press + motion-notify on the WebView - Does hit-test in C (8px border zone) - Calls gtk_window_begin_resize_drag with real event data - Returns TRUE to consume border clicks, FALSE for interior --- agor-pty/native/agor_resize.c | 222 +++++++++++++++++------------- agor-pty/native/libagor-resize.so | Bin 16424 -> 16688 bytes 2 files changed, 129 insertions(+), 93 deletions(-) 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 cc3db4e8bdfc98cae0698edebde963ece3f32e52..192eca7003d2bf4f4cc1e2f90903f79537c9a974 100755 GIT binary patch literal 16688 zcmeHO4RBP~b-ohF{1{k4493Qdo*l~(24oo`tfDX+4U^ahBkMXflmp5{f;f)znS9JHn2TaUwf} z?RW0IXZ7~6>vS@iPN%b1qdoV0=jWb#-hIFK-aqy2XsN8I5M1VnKNho`5MC{4*vuM) z-7Oa2_Xe>_>du`NT~XV&P(e&Zwc@FbDynMUO>bPF^q8`o>|}d}k_#8@nxk;0PP?lh zO0QG$^p-+;u2S(+nz9|SWH+wu#^#CN~9 z>4Q_R{p6e9-~HhyS5hn_rwZ9od`~wU3q){|)$`-oIq$KT!^RI=g{tE1m3!jo$9bLhW$ERr%?vDC+ zw7x-dfdWo-aNYX)hP*}x)iw$%C|z1sdpxd&2%tznY}>JYYm>FUetrE0jH3$IU*#2i zrEp_pLxt!ya?DCrB(4wRPFg880;9tBJ-$y>>x;sezOOMpu21q2uQU^kGoQXs;dD*s zdk`UG4t$Vz zIRoVk{2$JMIdp!$nX3AS+k`L=rtQkyxHPq*Uixa40YzLNN~!^W+x=Sm3VqY$&O3@sd^zoBj9B*hqr8kf;n8Z zp2(&ddpXj*3p$kU1-S`N%>hz8rYZCnNc|0!L{?umlT+r=ckeTg&df0@j+>`0+tn;)nm$~Z$zRxu%+!`|0na>)UStCJIbQWm z@D+Q}cD{YG&-Ovs2AkyIxn$z(v}dVr96vk9QdL11Bz>1co?oV?J-1TjHs84CYiZ9b z>7;Ka^qa@o+Jw4q@- zSv87uFq4BBGntq$lWk{lDB6)=I+pXyp}`4}SSTaR)}RG5<(o)(?;M(` zur<0sqE)w%$#C06b9m>Bnfzxnc_QWb;r6?WRYQfiuaBJznfWG7N>ml~7Q%3Jt9 z#jPDThd2BiP8$R_N@>WPzN8{dwh-XR63B zB316vDtBiw9Jx0tcXdc|(swb_hY(B|;$M=tJYV`U>dCY@e2x5Ag)`hlx>9p1&1cQw<@ZwRD#mjY!2carBY2#d{4AJ% zW#)b`Z!xnK%o%1jfH}=f9hjdmvkJ_QnOOqnBr_Ewh!-bFQ|5^eb?1B-TgT1hzhq`4 zLPM$z|LB?O@F{|6%-S_{*eD0P08K6>o!6#?|}kFgglnlaUJX?-t*c@IwS9X|A85vaOUNoIuiYq|oWf;nXN5)GFOd=;TbCuE&boq#|A4dm{ zAQhBeX!_EbH3{(x`iMo93?-01jH<74A<^<$6Iy?roMX&fKmzE z)1xpx_DN+-V`g|m)dFdway~{%=;-Tn>6C=0|I$F?_o$PNQcG5GA{?i&F}&f|y3u<- zLiHUw198>Ss!O4o`lI(`9F$p(G5c$%CFufFqhd_H39DnuN=0~!Y-R6E{t{E29J~aB zcW;rM!W9og>8P}r9ZDDASDK&1VzIRpPcGVmuf0kcj` z!0a_zeGhDZ#AgICGqR;NV6@v2BK-qf>T2s7LTl+~wGoR(?E=8wQkz&PBHD|HbES_f zL9RsdP$ZAV^H@BO3Gv&Dx!g(oFML0jdmD5L|JR~^2J~jo!nl2{t@pQXHX>2m@EhCg z#~zfEWt)xKUOXPtR;a~SFBI(w+m1%o^&-jfb#kgn^hE72AjBIE4QQ-b zoZB%Y7M62>0ScrxZq&6$W0?5EVd?FMZ&4VJb2d%RRT&-r?(Sd!q1j#e!&=v5bonEJ z?qJM_cSRH30V5DK?5=R!@F(nOkKYcH)d0B{6|3glgU<_czD2y7%k4wG;g`AG)1V)K z9s<4kLN1pE-8-GjO@J=KF`~(t^sjKN=%EvZ=N2lSXcZOx)fKB2&6__0+0BsA8h~ft z$>nIyRxGJ*S+ep|o~!%j9}xFlf6uLV+ej_lIJ+JyZ?2<>($*2Z`6y& zS~UHPrXfxDYx+e^zpCjAn!cjxaZS%?`k$KqO4FvIBaWb z+HBM{2iwE`h;jR!SRL80?)FWqb=BRILY|JtTycTs?FtHw?@EmFeY!msF46di3%^n0 ze15V1U8q!GG#&CPgpYrd#FypAzmf{^C@R!0pRbJXm-}+-;w&)io;%| z$>a6!B)&pCt8tpLr9$&~)b4e9F`#@$1~#toHF`0id^ZM`(fFV~apXHOuu}Q_P|B)B z;1j60O0oFo%S3td`y1F~74S;2IDegCoYv!4U}Gv1_e9!HGtmiWhn z`#ktL@Jc$%^*Ckyhk@&qF(m2!68O_4@I%0j0uPFzycY9MlXS)K(-OaOJ^R}lcdtW# zA9%5LX{xjsz6SVBMdEA$UM!wRO5lkS_~%RDUjt6-@eg?m8~;uT{ntz2Kh=JYxct0X zLVp^#>r|HkRN+EhtiLOPS7XJidw)Pp3H>`t;M+>z50$_pC2-LZv*UIm6sqqKv)cqRwa(?Qts$6{FJk45pZ z0TGH}F~1c^^z;nC#KB?LK-ex&b$otASaGx(4CpNYLVdRc7qGhoePFgiSoz-@jpGYv zG$OENqA%FKAD>Yz^}W=R-%Fji`e=$6T3Q=-`YhkBX4+q2VfT^cGc{;7w+h(>VQt%S z|JKGG*8MFl5BT<2dm6Xy@R2%pVB}2$@u;P~gbJ(q;a!b8w>SOnjTD8O3rg*ZaBTx| z?RrofI^_O?{Kf@G@?>UT{byn`8A&d{G>V5H@A+snQFTEp1)W-*UY=1kb z9aFT6YxuMutnZ8@>f008gR(9h5R&Wi$Gb#*U?2iZMeUf9><`A`bRtMK%~+7dg57>H z(6wIdxT%*LW$IC_?~H=AgZ=oW6BkNRxx=JB*riX^t^k}cr%cs3t1P&V7W_To4m2ON z$uokZ%`f%Xs3Pid)#$;+pzsR)`*HNXK##__{K`^lU!)cn>@6V->kkE{6{2jvPgj`U ztqpP6l!c#e_9n6JP;+w*${(@`y;=O^4k^Y1nM{O<)uzS*ANTbS~D3RGkvAI@Te zp?3zh=l2Aro3%2>&w5O0-yFSzFwgHHObu=Cj$gwffC|ajp5J?zrnMrs&-vs2N3{J` zt;g?Cd8#{1_Ox%rZQl=!RQNqF|J!qA$U(mqCK z;kX(51}c<4Rx@r=5~j_psHwaC7lAwNyR0OJ*7w<>o{l{*7=9&Hg zwjPH)e`grOl3FUPr?^?3@gG4zV~^YC_W|DDJw=^E;eN9nAIGO4BNevieQ@d9$V$+X zyMn^@On(Zg)1KdJGTQ!df%TQ~|J-Fi;#CGC4T|Q!-zfHqe7p^uV&V7?d6nXG+MfHV z(AoBx&!9r_YjuVN+t=g~Rnh6p4-4WeT{^_3wZj>mc>0b+-K9x>Zl4wDba$yp(Y>3L#syZ?)NTLY0VHy% literal 16424 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