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
This commit is contained in:
parent
f97eaa1c36
commit
683013d704
2 changed files with 129 additions and 93 deletions
|
|
@ -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).
|
* KEY INSIGHT: Signals MUST be connected on the WebKitWebView widget,
|
||||||
* Stores mouse state from the real GTK event. Exports start_resize(edge)
|
* NOT the GtkWindow. WebKit's GdkWindow receives all pointer events;
|
||||||
* for Bun FFI to call — uses the stored event data for begin_resize_drag.
|
* 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 \
|
* Build: gcc -shared -fPIC -o libagor-resize.so agor_resize.c \
|
||||||
* $(pkg-config --cflags --libs gtk+-3.0)
|
* $(pkg-config --cflags --libs gtk+-3.0)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
#include <gdk/gdkx.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
/* 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;
|
static GtkWindow *stored_window = NULL;
|
||||||
|
|
||||||
/* Border width for hit-test (pixels) */
|
|
||||||
static int border_width = 8;
|
static int border_width = 8;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GTK button-press-event handler — stores mouse state for later use.
|
* Hit-test: determine which edge the pointer is on.
|
||||||
* Connected directly in C — no JSCallback boundary crossing.
|
* Returns GdkWindowEdge (0-7) or -1 if inside content area.
|
||||||
* Returns FALSE to let the event propagate to WebKitGTK normally.
|
* x, y are window-local coordinates (same as webview coords when it fills the window).
|
||||||
*/
|
*/
|
||||||
static gboolean
|
static GdkWindowEdge edge_for_position(int win_w, int win_h, double x, double y)
|
||||||
on_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
|
|
||||||
{
|
{
|
||||||
if (event == NULL) return FALSE;
|
int B = border_width;
|
||||||
if (event->button == 3) return FALSE; /* Skip right-click */
|
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) {
|
if (top && left) return GDK_WINDOW_EDGE_NORTH_WEST;
|
||||||
stored_xroot = event->x_root;
|
if (top && right) return GDK_WINDOW_EDGE_NORTH_EAST;
|
||||||
stored_yroot = event->y_root;
|
if (bottom && left) return GDK_WINDOW_EDGE_SOUTH_WEST;
|
||||||
stored_time = event->time;
|
if (bottom && right) return GDK_WINDOW_EDGE_SOUTH_EAST;
|
||||||
stored_button = event->button;
|
if (top) return GDK_WINDOW_EDGE_NORTH;
|
||||||
}
|
if (bottom) return GDK_WINDOW_EDGE_SOUTH;
|
||||||
return FALSE; /* Propagate — WebKitGTK still processes the event */
|
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.
|
* Button-press handler on the WebKitWebView.
|
||||||
* Call ONCE after window creation from Bun FFI.
|
* 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.
|
||||||
* @param window_ptr GtkWindow* (from Electrobun's mainWindow.ptr)
|
*/
|
||||||
* @param border Border width in pixels for edge detection
|
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)
|
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);
|
stored_window = GTK_WINDOW(window_ptr);
|
||||||
border_width = border > 0 ? border : 8;
|
border_width = border > 0 ? border : 8;
|
||||||
|
|
||||||
/* Add event masks */
|
/* Walk down the widget tree to find the deepest child (the WebKitWebView) */
|
||||||
gtk_widget_add_events(GTK_WIDGET(stored_window),
|
GtkWidget *webview = GTK_WIDGET(stored_window);
|
||||||
GDK_POINTER_MOTION_MASK |
|
while (GTK_IS_BIN(webview)) {
|
||||||
GDK_BUTTON_PRESS_MASK |
|
GtkWidget *child = gtk_bin_get_child(GTK_BIN(webview));
|
||||||
GDK_BUTTON1_MOTION_MASK);
|
if (!child) break;
|
||||||
|
webview = child;
|
||||||
/* 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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(stderr, "[agor-resize] Initialized: window=%p border=%d\n",
|
if (webview == GTK_WIDGET(stored_window)) {
|
||||||
window_ptr, border_width);
|
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.
|
* Start resize from stored state (for RPC fallback — not needed if C handler works).
|
||||||
* 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
|
|
||||||
*/
|
*/
|
||||||
int agor_resize_start(int edge)
|
int agor_resize_start(int edge)
|
||||||
{
|
{
|
||||||
if (!stored_window) {
|
fprintf(stderr, "[agor-resize] agor_resize_start called with edge=%d (C handler should do this automatically)\n", edge);
|
||||||
fprintf(stderr, "[agor-resize] ERROR: not initialized\n");
|
|
||||||
return 0;
|
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;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue