[GTK4]Non-rectangular window with GTK4 under Linux

Hello,
I want to create a non-rectangular window, like a donut, with GTK4, the mouse event can get through the middle of donut to the underneath window if any.
Can anyone give a direction on how to do it? Thank you in advance,

1 Like

Hello, it should be possible with the following steps:

  1. Create a GtkWindow subclass
  2. Override the size_allocate method
  3. After chaining up to the parent size_allocate, get the window’s surface and make a call to gdk_surface_set_input_region with the desired shape

Thank you very much, I can now create a donut form window with this method and make the mouse event get through the center of the donut to its underneath window of another application. That’s great!
Here is the code if anyone needs it:

#include <gtk/gtk.h>

typedef struct
{
  GtkWindow parent_instance;
  cairo_surface_t *surface;
  cairo_region_t * input_region;
} MyGtkWindow;

typedef struct
{
  GtkWindowClass parent_class;
} MyGtkWindowClass;

static GType my_gtk_window_get_type (void);
G_DEFINE_TYPE (MyGtkWindow, my_gtk_window, GTK_TYPE_WINDOW)

static void
my_gtk_window_size_allocate (GtkWidget *widget,
                            int        width,
                            int        height,
                            int        baseline)
{
	MyGtkWindow *window = (MyGtkWindow *) widget;

	GTK_WIDGET_CLASS(my_gtk_window_parent_class)->size_allocate (widget, width, height, baseline);

	GtkNative * native = gtk_widget_get_native(widget);
	GdkSurface * surface = gtk_native_get_surface(native);
	gdk_surface_set_input_region(surface, window->input_region);
}

static void
my_gtk_window_snapshot (GtkWidget   *widget,
		       GtkSnapshot *snapshot)
{
	MyGtkWindow *window = (MyGtkWindow *) widget;
	GtkAllocation allocation;
	gtk_widget_get_allocation(widget, &allocation);
	graphene_rect_t rect = {0, 0, allocation.width, allocation.height};

	cairo_t * cr = gtk_snapshot_append_cairo(snapshot, &rect);

	cairo_set_source_surface(cr, window->surface, 0, 0);
	cairo_paint(cr);

	cairo_destroy (cr);
}

static void
my_gtk_window_class_init(MyGtkWindowClass *klass)
{
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
  widget_class->size_allocate = my_gtk_window_size_allocate;
  widget_class->snapshot = my_gtk_window_snapshot;
}

static void
my_gtk_window_init(MyGtkWindow *window)
{
	window->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 400, 400);
	cairo_rectangle_int_t rect = {0, 0, 400, 400};
	cairo_rectangle_int_t hole = {100, 100, 200, 200};

	window->input_region = cairo_region_create_rectangle(&rect);
	cairo_region_subtract_rectangle( window->input_region, &hole);

	cairo_t * cr = cairo_create(window->surface);
	cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
	cairo_set_source_rgba(cr, 0, 0, 1, 1);
	cairo_rectangle(cr, rect.x, rect.y, rect.width, rect.height);
	cairo_fill(cr);

    cairo_set_source_rgba(cr, 1, 0, 0, 0);
	cairo_rectangle(cr, hole.x, hole.y, hole.width, hole.height);
	cairo_fill(cr);

    cairo_destroy(cr);
}

static GtkWidget *
my_gtk_window_new (void)
{
  return (GtkWidget *)g_object_new(my_gtk_window_get_type(), NULL);
}

static void set_transparent_background(GtkWindow * window)
{
	GtkStyleContext *context;
	GtkCssProvider *provider;

	context = gtk_widget_get_style_context(GTK_WIDGET(window));
	provider = gtk_css_provider_new ();

	gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(provider), "window {background: transparent;}", -1);

	gtk_style_context_add_provider (context,
                                GTK_STYLE_PROVIDER (provider),
                                GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
	g_object_unref(provider);

}

static void app_activate (GApplication *app, gpointer *user_data)
{
	GtkWidget *window;
	window = my_gtk_window_new ();
	gtk_window_set_default_size(GTK_WINDOW(window), 500, 500);
	gtk_window_set_title(GTK_WINDOW(window), "Transparent window");
	gtk_window_set_decorated(GTK_WINDOW(window), FALSE);
	set_transparent_background(GTK_WINDOW(window));

	gtk_window_set_application (GTK_WINDOW (window), GTK_APPLICATION (app));
	gtk_window_present (GTK_WINDOW (window));
}

int main (int argc, char **argv)
{
	GtkApplication *app;
	int stat;

	app = gtk_application_new ("com.test.hello-gtk4", G_APPLICATION_FLAGS_NONE);
	g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);

   	stat =g_application_run (G_APPLICATION (app), argc, argv);
	g_object_unref (app);
	return stat;
}


1 Like

Hello,
I have a problem with the mouse event under gnome wayland.

I need to use “x11” back-end by gdk_set_allowed_backends()
and put the donut window at a specified position of the screen. But if I do this, the mouse event can no longer get through the center of the donut.

Thank you in advance for your help!

Here is the modification on my_gtk_window_size_allocate() as an example:

static void
my_gtk_window_size_allocate (GtkWidget *widget,
                            int        width,
                            int        height,
                            int        baseline)
{
	MyGtkWindow *window = (MyGtkWindow *) widget;

	GTK_WIDGET_CLASS(my_gtk_window_parent_class)->size_allocate (widget, width, height, baseline);

	GtkNative * native = gtk_widget_get_native(widget);
	GdkSurface * surface = gtk_native_get_surface(native);

	Window xwnd = gdk_x11_surface_get_xid(surface);

	GdkDisplay * display = gtk_widget_get_display(widget);
	Display * xDisplay = gdk_x11_display_get_xdisplay(display);

	XMoveWindow(xDisplay, xwnd, 200, 300);
}

Hello,
I want to understand the problem under GNOME Wayland desktop if we use XWayland. Is it a problem XWayland that supports poorly XShape extension?
Thank you in advance for your inputs.

Hello,
I found a little astute by several times try and test.
It seems that if we put the call gdk_surface_set_input_region() at the subclass override method realize() instead of in the size-allocate() method, the center of donut becomes mouse-event transparent under xWayland.
But why? Here is the complete code if anybody hopes to help me. I need to understand it to update the input region if the donut changes.
I prefer to note that the question concerns only xWayland (x11 backend under wayland).

Thank you in advance,

#include <gtk/gtk.h>
#include <gdk/x11/gdkx.h>

typedef struct
{
  GtkWindow parent_instance;
  cairo_surface_t *surface;
  cairo_region_t * input_region;
} MyGtkWindow;

typedef struct
{
  GtkWindowClass parent_class;
} MyGtkWindowClass;

static GType my_gtk_window_get_type (void);
G_DEFINE_TYPE (MyGtkWindow, my_gtk_window, GTK_TYPE_WINDOW)

static void
my_gtk_window_realize (GtkWidget *widget)
{
	GTK_WIDGET_CLASS (my_gtk_window_parent_class)->realize(widget);

	MyGtkWindow *window = (MyGtkWindow *) widget;
	GtkNative * native = gtk_widget_get_native(widget);
	GdkSurface * surface = gtk_native_get_surface(native);

	gdk_surface_set_input_region(surface, window->input_region);
}

static void
my_gtk_window_map(GtkWidget *widget)
{
	GTK_WIDGET_CLASS (my_gtk_window_parent_class)->map(widget);

	MyGtkWindow *window = (MyGtkWindow *) widget;

	GtkNative * native = gtk_widget_get_native(widget);
	GdkSurface * surface = gtk_native_get_surface(native);

	Window xwnd = gdk_x11_surface_get_xid(surface);

	GdkDisplay * display = gtk_widget_get_display(widget);
	Display * xDisplay = gdk_x11_display_get_xdisplay(display);

	XMoveWindow(xDisplay, xwnd, 500, 300);
}

static void
my_gtk_window_snapshot (GtkWidget   *widget,
		       GtkSnapshot *snapshot)
{
	GtkAllocation allocation;
	gtk_widget_get_allocation(widget, &allocation);
	graphene_rect_t rect = {0, 0, allocation.width, allocation.height};

	cairo_t * cr = gtk_snapshot_append_cairo(snapshot, &rect);

	MyGtkWindow *window = (MyGtkWindow *) widget;
	cairo_set_source_surface(cr, window->surface, 0, 0);
	cairo_paint(cr);

	cairo_destroy (cr);
}

static void
my_gtk_window_class_init(MyGtkWindowClass *klass)
{
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
  widget_class->realize = my_gtk_window_realize;
  widget_class->map = my_gtk_window_map;
  widget_class->snapshot = my_gtk_window_snapshot;
}

static void
my_gtk_window_init(MyGtkWindow *window)
{
	window->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 400, 400);
	cairo_rectangle_int_t rect = {0, 0, 400, 400};
	cairo_rectangle_int_t hole = {100, 100, 200, 200};


	window->input_region = cairo_region_create_rectangle(&rect);
	cairo_region_subtract_rectangle( window->input_region, &hole);

	cairo_t * cr = cairo_create(window->surface);
	cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
	cairo_set_source_rgba(cr, 0, 0, 1, 1);
	cairo_rectangle(cr, rect.x, rect.y, rect.width, rect.height);
	cairo_fill(cr);

    cairo_set_source_rgba(cr, 1, 0, 0, 0);
	cairo_rectangle(cr, hole.x, hole.y, hole.width, hole.height);
	cairo_fill(cr);

    cairo_destroy(cr);
}

static GtkWidget *
my_gtk_window_new (void)
{
  return (GtkWidget *)g_object_new(my_gtk_window_get_type(), NULL);
}

static void set_transparent_background(GtkWindow * window)
{
	GtkStyleContext *context;
	GtkCssProvider *provider;

	context = gtk_widget_get_style_context(GTK_WIDGET(window));
	provider = gtk_css_provider_new ();

	gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(provider), "window {background: transparent;}", -1);

	gtk_style_context_add_provider (context,
                                GTK_STYLE_PROVIDER (provider),
                                GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
	g_object_unref(provider);

}

static void app_activate (GApplication *app, gpointer *user_data)
{
	GtkWidget *window;
	window = my_gtk_window_new ();
	gtk_window_set_default_size(GTK_WINDOW(window), 500, 500);
	gtk_window_set_title(GTK_WINDOW(window), "Transparent window");
	gtk_window_set_decorated(GTK_WINDOW(window), FALSE);
	set_transparent_background(GTK_WINDOW(window));

	gtk_window_set_application (GTK_WINDOW (window), GTK_APPLICATION (app));
	gtk_window_present (GTK_WINDOW (window));
}

int main (int argc, char **argv)
{
	gdk_set_allowed_backends("x11");
	GtkApplication *app;
	int stat;

	app = gtk_application_new ("com.test.hello-gtk4", G_APPLICATION_FLAGS_NONE);
	g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);

   	stat =g_application_run (G_APPLICATION (app), argc, argv);
	g_object_unref (app);
	return stat;
}

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.