Using gtk4, in C, how do I make the window background color transparent?

I’m not sure how to override the snapshot virtual function for a widget, in C. I’m using C++, but I’m not using gtkmm. I can’t use gtkmm and the code below should compile in C. I’m using this blog post as an example, Custom widgets in GTK 4 – Drawing – GTK Development Blog.

Example code:

/* Hello World application in gtk4, trying to set the background 
 * color of the main window to transparent.
 */

#include <gtk/gtk.h>
//#include <iostream>

// doesn't get called?
void demo_snapshot(GtkWidget* widget, GtkSnapshot* snapshot) {
  
  g_print("Snapshot!\n");
  // checked with std::printf, also doesnt work
  //std::printf("Snapshot!\n");
}

static void print_hello(GtkWidget* widget, gpointer data) {
  g_print("Hello World\n");
  //std::printf("Hello World\n");
}

static void activate(GtkApplication* app, gpointer user_data) {
  
#define WIDTH 800
#define HEIGHT 600

  GtkWidget* window;
  GtkWidget* button;
  GtkWidget* fixed_container;

  window = gtk_application_window_new(app);
  gtk_window_set_title(GTK_WINDOW(window), "Hello");
  gtk_window_set_default_size(GTK_WINDOW(window), WIDTH, HEIGHT);
  
  // set virtual function, like in example
  ((GtkWidgetClass*)window)->snapshot = demo_snapshot;
  // doesn't call demo_snapshot
  //gtk_widget_queue_draw(window);

  button = gtk_button_new_with_label("Hello World");
  g_signal_connect(button, "clicked", G_CALLBACK(print_hello), NULL);
  
  fixed_container = gtk_fixed_new();
  gtk_window_set_child(GTK_WINDOW(window), fixed_container);

  gtk_fixed_put(GTK_FIXED(fixed_container), button, 20, 20);
  gtk_widget_set_size_request(button, 400, 100);

  // set virtual function for button
  ((GtkWidgetClass*)button)->snapshot = demo_snapshot;

  gtk_window_present(GTK_WINDOW(window));

  // trying after window is presented, these still don't call demo_snapshot
  //gtk_widget_queue_draw(window);
  //gtk_widget_queue_draw(button);
}

int main(int argc, char* argv[]) {

  GtkApplication* app;

  app = gtk_application_new("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
  g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);

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

The result is a window with the “Hello World” button in the middle. The print_hello function is called when the button is pressed. I was expecting, when the widgets are drawn, it would call the snapshot function.
How do I call the demo_snapshot function in a widget?

That is emphatically not how anything works.

You will need to understand how to implement a GObject class before attempting to understand how to implement a custom snapshot virtual function.

Thank you ebassi for showing me how to subclass a GObject. This is my updated example code that subclasses GtkApplicationWindow. It works, but example_transparent_window_snapshot is not getting called. Do I have to call this function? I assumed it gets called when the window background is drawn.

I should also mention I’m new to using gtk. I’ve been reading the documentation for gtk4 but there is no description for the gtk_widget_snapshot virtual method. Gtk.Widget.snapshot

If GtkWindow classes can’t be snapshotted, how could I change the background color of the window to be transparent? A transparent GtkDrawingArea doesn’t work, because it makes itself transparent but not the window background underneath. I want to be able to do this in C, not CSS or any external files. I want to make the window background color transparent, but keep the child widgets opaque (buttons, etc.) so gtk_widget_set_opacity isn’t what im looking for.

/* Hello World application in gtk4, trying to set the background
 * color of the
 * main window to transparent.
 */

#include <gtk/gtk.h>

// new subclass

G_BEGIN_DECLS

#define EXAMPLE_TYPE_TRANSPARENT_WINDOW example_transparent_window_get_type ()
G_DECLARE_DERIVABLE_TYPE (ExampleTransparentWindow, example_transparent_window, EXAMPLE, TRANSPARENT_WINDOW, GtkApplicationWindow)

struct _ExampleTransparentWindowClass
{
  GObject parent_instance;

  void (*snapshot) (
      GtkWidget *widget, GtkSnapshot *snapshot);
  /* Other members, including private data. */

  /* Padding to allow adding up to 12 new virtual functions without
   * breaking ABI. */
  gpointer padding[12];
};

GtkWidget *example_transparent_window_new (GtkApplication *application);

void example_transparent_window_snapshot (
    GtkWidget *widget, GtkSnapshot *snapshot);

G_END_DECLS

/*
 * forward definitions
 */

G_DEFINE_TYPE (ExampleTransparentWindow, example_transparent_window, GTK_TYPE_APPLICATION_WINDOW)

static void
example_transparent_window_real_snapshot (
    GtkWidget *widget, GtkSnapshot *snapshot)
{

  /* Default implementation for the virtual method. */
  g_print ("snapshot\n");
}

void
example_transparent_window_snapshot (
    GtkWidget *widget, GtkSnapshot *snapshot)
{

  ExampleTransparentWindowClass *klass;

  klass =
      EXAMPLE_TRANSPARENT_WINDOW_GET_CLASS (EXAMPLE_TRANSPARENT_WINDOW (widget));

  // not called
  puts ("got here?");

  /* if the method is purely virtual, then it is a good idea to
   * check that it has been overridden before calling it, and,
   * depending on the intent of the class, either ignore it silently
   * or warn the user.
   */
  g_return_if_fail (klass->snapshot != NULL);

  klass->snapshot (widget, snapshot);
}

static void
example_transparent_window_class_init (
    ExampleTransparentWindowClass *klass)
{

  /* merely virtual method. */
  klass->snapshot = example_transparent_window_real_snapshot;
}

static void
example_transparent_window_init (ExampleTransparentWindow *self)
{
  // ViewerFilePrivate *priv = viewer_file_get_instance_private (self);

  /* initialize all public and private members to reasonable default values.
   * They are all automatically initialized to 0 to begin with. */
}

static void
example_transparent_window_dispose (GObject *gobject)
{
  // ViewerFilePrivate *priv = viewer_file_get_instance_private (VIEWER_FILE (gobject));

  /* In dispose(), you are supposed to free all types referenced from this
   * object which might themselves hold a reference to self. Generally,
   * the most simple solution is to unref all members on which you own a
   * reference.
   */

  /* dispose() might be called multiple times, so we must guard against
   * calling g_object_unref() on an invalid GObject by setting the member
   * NULL; g_clear_object() does this for us.
   */
  // g_clear_object (&priv->input_stream);

  /* Always chain up to the parent class; there is no need to check if
   * the parent class implements the dispose() virtual function: it is
   * always guaranteed to do so
   */
  G_OBJECT_CLASS (example_transparent_window_parent_class)->dispose (gobject);
}

static void
example_transparent_window_finalize (GObject *gobject)
{
  // ViewerFilePrivate *priv = viewer_file_get_instance_private (VIEWER_FILE (gobject));

  /* Always chain up to the parent class; as with dispose(), finalize()
   * is guaranteed to exist on the parent's class virtual function table
   */
  G_OBJECT_CLASS (example_transparent_window_parent_class)->finalize (gobject);
}

// copied from gtk4 application window source code
GtkWidget *
example_transparent_window_new (GtkApplication *application)
{

  g_return_val_if_fail (GTK_IS_APPLICATION (application), NULL);

  // using EXAMPLE_TYPE_TRANSPARENT_WINDOW throws critical runtime errors
  return (GtkWidget *) g_object_new (GTK_TYPE_APPLICATION_WINDOW, "application",
                                     application, NULL);
}
// end gtk4 source code
// end new subclass

static void
print_hello (GtkWidget *widget, gpointer data)
{
  g_print ("Hello World\n");
  // std::printf("Hello World\n");
}

static void
activate (GtkApplication *app, gpointer user_data)
{

#define WIDTH 800
#define HEIGHT 600

  GtkWidget *window;
  GtkWidget *button;
  GtkWidget *fixed_container;

  // works, but `example_transparent_window_snapshot` isn't getting called
  window = example_transparent_window_new (app);

  // window = gtk_application_window_new(app);
  gtk_window_set_title (GTK_WINDOW (window), "Hello");
  gtk_window_set_default_size (GTK_WINDOW (window), WIDTH, HEIGHT);

  button = gtk_button_new_with_label ("Hello World");
  g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);

  fixed_container = gtk_fixed_new ();
  gtk_window_set_child (GTK_WINDOW (window), fixed_container);

  gtk_fixed_put (GTK_FIXED (fixed_container), button, 20, 20);
  gtk_widget_set_size_request (button, 400, 100);

  gtk_window_present (GTK_WINDOW (window));
}

int
main (int argc, char *argv[])
{

  GtkApplication *app;

  app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);

  g_application_run (G_APPLICATION (app), argc, argv);

  g_object_unref (app);
  return 0;
}

Thank you,
livphi

Hi,

I’m not sure using a custom snapshot will work, as gtk will anyway automatically draw the background based on CSS theme before calling snapshot().

Why not embedding a small CSS style to unset the window’s background?

Here a working example in Python:

#!/usr/bin/env python3

import sys
import gi
gi.require_version('Gdk', '4.0')
gi.require_version('Gtk', '4.0')
from gi.repository import Gdk, Gtk

THEME = """
window.background {
    background: unset;
}
"""

class MyAppWindow(Gtk.ApplicationWindow):
    __gtype_name__ = __qualname__
    def __init__(self, **kwargs):
        super().__init__(default_width=200, default_height=200, show_menubar=False, **kwargs)
        button = Gtk.Button.new_with_label("Hello World!")
        button.set_halign(Gtk.Align.CENTER)
        button.set_valign(Gtk.Align.CENTER)
        self.set_child(button)
        self.present()

class MyApp(Gtk.Application):
    __gtype_name__ = __qualname__
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.connect('activate', self.on_activate)
    def on_activate(self, app):
        cssp = Gtk.CssProvider()
        cssp.load_from_string(THEME)
        d = Gdk.Display.get_default()
        Gtk.StyleContext.add_provider_for_display(d, cssp, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
        self.add_window(MyAppWindow())

if __name__ == '__main__':
    sys.exit(MyApp().run(sys.argv))

Hi gwillems,
I could do that, but i’d prefer to do it without using CSS if possible. Also, I noticed changing light/dark mode changes the window background, I’m not too sure how that works, does it use CSS?

I did it in C++. I checked and it also compiles and runs as C. If someone knows how to do this without using CSS please reply here. Thank you gwillems and ebassi :slight_smile: Here is the source code:

#include <gtk/gtk.h>

static void
print_hello (GtkWidget *widget, gpointer data)
{
  g_print ("Hello World\n");
}

static void
activate (GtkApplication *app, gpointer user_data)
{

#define WIDTH 800
#define HEIGHT 600

  GtkWidget *window;
  GtkWidget *button;
  GtkWidget *fixed_container;
  GtkCssProvider *css_provider;

  window = gtk_application_window_new (app);

  // css stuff
  css_provider = gtk_css_provider_new ();
  gtk_css_provider_load_from_string (
      css_provider,
      // rbga, `a` set to 0.0 makes the window background transparent
      ".window { background-color: rgba(0, 0, 0, 0.0); }");

  gtk_style_context_add_provider_for_display (
      gtk_widget_get_display (window),
      (GtkStyleProvider *) css_provider, GTK_STYLE_PROVIDER_PRIORITY_USER);
  
  gtk_widget_add_css_class (window, "window");
  // end css stuff

  gtk_window_set_title (GTK_WINDOW (window), "Hello");
  gtk_window_set_default_size (GTK_WINDOW (window), WIDTH, HEIGHT);

  button = gtk_button_new_with_label ("Hello World");
  g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);

  fixed_container = gtk_fixed_new ();
  gtk_window_set_child (GTK_WINDOW (window), fixed_container);

  gtk_fixed_put (GTK_FIXED (fixed_container), button, 20, 20);
  gtk_widget_set_size_request (button, 400, 100);

  gtk_window_present (GTK_WINDOW (window));
}

int
main (int argc, char *argv[])
{

  GtkApplication *app;

  app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
  g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);

  int retval = g_application_run (G_APPLICATION (app), argc, argv);

  g_object_unref (app);
  return retval;
}

I found that other thread that could be interesting:

1 Like

Hello,
Here is what I read from https://docs.gtk.org/gtk4/migrating-3to4.html:

Stop using gtk_widget_set_app_paintable

This is gone in GTK4 with no direct replacement. But for some usecases there are alternatives. If you want to make the background transparent, you can set the background color to, for example, rgba(255, 255, 255, 0) using CSS instead.