17

I'd like to make the background of a Gtk+ window transparent so that only the widgets in the window are visible. I found a few tutorials:

http://mikehearn.wordpress.com/2006/03/26/gtk-windows-with-alpha-channels/

http://macslow.thepimp.net/?p=26

But they both appear to listen to the "expose" event, and then delegate to Cairo to do the rendering. This means that other widgets added to the window are not rendered (for example, I've tried adding a button to the window as well).

I see that there's a method modify-bg on GtkWidget: http://library.gnome.org/devel/gtk/stable/GtkWidget.html#gtk-widget-modify-bg

However, GdkColor does not appear to accept a parameter for transparency: http://library.gnome.org/devel/gdk/stable/gdk-Colormaps-and-Colors.html

I've tried the GtkWindow.set_opacity method as well, but this sets the opacity for the window contents as well, which is not what I want.

I'd appreciate any guidance anyone can provide in this.

izidor
  • 4,068
  • 5
  • 33
  • 43
jbeard4
  • 12,664
  • 4
  • 57
  • 67

5 Answers5

20

I changed the alphademo example to draw a button instead of the red circle.

This application draws the button on a 400x400 transparent window.

When you click on the window the application shows/hides the title bar.

#include <gtk/gtk.h>
#include <gdk/gdkscreen.h>
#include <cairo.h>

static void screen_changed(GtkWidget *widget, GdkScreen *old_screen, gpointer user_data);
static gboolean expose(GtkWidget *widget, GdkEventExpose *event, gpointer user_data);
static void clicked(GtkWindow *win, GdkEventButton *event, gpointer user_data);

int main(int argc, char **argv)
{
    gtk_init(&argc, &argv);

    GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
    gtk_window_set_default_size(GTK_WINDOW(window), 400, 400);
    gtk_window_set_title(GTK_WINDOW(window), "Alpha Demo");
    g_signal_connect(G_OBJECT(window), "delete-event", gtk_main_quit, NULL);

    gtk_widget_set_app_paintable(window, TRUE);

    g_signal_connect(G_OBJECT(window), "expose-event", G_CALLBACK(expose), NULL);
    g_signal_connect(G_OBJECT(window), "screen-changed", G_CALLBACK(screen_changed), NULL);

    gtk_window_set_decorated(GTK_WINDOW(window), FALSE);
    gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK);
    g_signal_connect(G_OBJECT(window), "button-press-event", G_CALLBACK(clicked), NULL);

    GtkWidget* fixed_container = gtk_fixed_new();
    gtk_container_add(GTK_CONTAINER(window), fixed_container);
    GtkWidget* button = gtk_button_new_with_label("button1");
    gtk_widget_set_size_request(button, 100, 100);
    gtk_container_add(GTK_CONTAINER(fixed_container), button);

    screen_changed(window, NULL, NULL);

    gtk_widget_show_all(window);
    gtk_main();

    return 0;
}


gboolean supports_alpha = FALSE;
static void screen_changed(GtkWidget *widget, GdkScreen *old_screen, gpointer userdata)
{
    /* To check if the display supports alpha channels, get the colormap */
    GdkScreen *screen = gtk_widget_get_screen(widget);
    GdkColormap *colormap = gdk_screen_get_rgba_colormap(screen);

    if (!colormap)
    {
        printf("Your screen does not support alpha channels!\n");
        colormap = gdk_screen_get_rgb_colormap(screen);
        supports_alpha = FALSE;
    }
    else
    {
        printf("Your screen supports alpha channels!\n");
        supports_alpha = TRUE;
    }

    gtk_widget_set_colormap(widget, colormap);
}

static gboolean expose(GtkWidget *widget, GdkEventExpose *event, gpointer userdata)
{
   cairo_t *cr = gdk_cairo_create(widget->window);

    if (supports_alpha)
        cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.0); /* transparent */
    else
        cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); /* opaque white */

    /* draw the background */
    cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
    cairo_paint (cr);

    cairo_destroy(cr);

    return FALSE;
}

static void clicked(GtkWindow *win, GdkEventButton *event, gpointer user_data)
{
    /* toggle window manager frames */
    gtk_window_set_decorated(win, !gtk_window_get_decorated(win));
}

Compiled on Ubuntu 10.04:

gcc alpha.c -o alpha -I/usr/include/gtk-2.0 -I/usr/lib/gtk-2.0/include -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include/cairo -I/usr/include/pango-1.0 -I/usr/include/atk-1.0 -lgtk-x11-2.0
Afriza N. Arief
  • 7,696
  • 5
  • 47
  • 74
karlphillip
  • 92,053
  • 36
  • 243
  • 426
  • Notice that I add a fixed_container to the window and then attach the button to it. – karlphillip Oct 11 '10 at 19:26
  • 1
    Works great. I also tried using it with gtk webkit with its background set to transparent, and it worked flawlessly. – jbeard4 Oct 12 '10 at 09:36
  • 2
    @echo-flow: could you please add your gtk-webkit answer with an example? Thanks. – neydroydrec May 07 '11 at 10:17
  • 4
    Note: Rather than listing all the library file locations one by one for compilation, better use `pkg-config`: `gcc alpha.c -o alpha $( pkg-config --cflags --libs gtk+-2.0 )`. This also works if your system happens to have some library installed in a non-standard (or new) path. – sleske Dec 20 '14 at 23:53
  • 1
    Original link is available on waybackmachine: https://web.archive.org/web/20121027002505/http://plan99.net/~mike/files/alphademo.c – Jamie Pate Jun 13 '16 at 18:06
  • Apologies, I know this is late, but would you happen to know what this means if the background shows up as black? @jbeard4 –  Jun 08 '17 at 15:00
16

Thanks for the answer. I rewrite the code in python + GTK3:

#!/usr/bin/env python3

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
import cairo


supports_alpha = False


def screen_changed(widget, old_screen, userdata=None):
    global supports_alpha

    screen = widget.get_screen()
    visual = screen.get_rgba_visual()

    if visual is None:
        print("Your screen does not support alpha channels!")
        visual = screen.get_system_visual()
        supports_alpha = False
    else:
        print("Your screen supports alpha channels!")
        supports_alpha = True

    widget.set_visual(visual)


def expose_draw(widget, event, userdata=None):
    global supports_alpha

    cr = Gdk.cairo_create(widget.get_window())

    if supports_alpha:
        print("setting transparent window")
        cr.set_source_rgba(1.0, 1.0, 1.0, 0.0) 
    else:
        print("setting opaque window")
        cr.set_source_rgb(1.0, 1.0, 1.0)

    cr.set_operator(cairo.OPERATOR_SOURCE)
    cr.paint()

    return False


def clicked(window, event, userdata=None):
    # toggle window manager frames
    window.set_decorated(not window.get_decorated())


if __name__ == "__main__":
    window = Gtk.Window()
    window.set_position(Gtk.WindowPosition.CENTER)
    window.set_default_size(400, 400)
    window.set_title("Alpha Demo")
    window.connect("delete-event", Gtk.main_quit)

    window.set_app_paintable(True)

    window.connect("draw", expose_draw)
    window.connect("screen-changed", screen_changed)

    window.set_decorated(False)
    window.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
    window.connect("button-press-event", clicked)

    fixed_container = Gtk.Fixed()
    window.add(fixed_container)
    button = Gtk.Button.new_with_label("button1")
    button.set_size_request(100, 100)
    fixed_container.add(button)

    screen_changed(window, None, None)

    window.show_all()
    Gtk.main()
Afriza N. Arief
  • 7,696
  • 5
  • 47
  • 74
fpemud
  • 363
  • 3
  • 10
13

Thanks for the code, just what i was looking for though it needs to be modified to work GTK3. Also the expose-event needs to be changed to a 'draw' event to get it working. So here's my code contribution modified for GTK3:

#include <gtk/gtk.h>
#include <gdk/gdkscreen.h>
#include <cairo.h>

static void screen_changed(GtkWidget *widget, GdkScreen *old_screen, gpointer user_data);
static gboolean expose_draw(GtkWidget *widget, GdkEventExpose *event, gpointer userdata);
static void clicked(GtkWindow *window, GdkEventButton *event, gpointer user_data);

int main(int argc, char **argv) {
    gtk_init(&argc, &argv);

    GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
    gtk_window_set_default_size(GTK_WINDOW(window), 400, 400);
    gtk_window_set_title(GTK_WINDOW(window), "Alpha Demo");
    g_signal_connect(G_OBJECT(window), "delete-event", gtk_main_quit, NULL);

    gtk_widget_set_app_paintable(window, TRUE);

    g_signal_connect(G_OBJECT(window), "draw", G_CALLBACK(expose_draw), NULL);
    g_signal_connect(G_OBJECT(window), "screen-changed", G_CALLBACK(screen_changed), NULL);

    gtk_window_set_decorated(GTK_WINDOW(window), FALSE);
    gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK);
    g_signal_connect(G_OBJECT(window), "button-press-event", G_CALLBACK(clicked), NULL);

    GtkWidget* fixed_container = gtk_fixed_new();
    gtk_container_add(GTK_CONTAINER(window), fixed_container);
    GtkWidget* button = gtk_button_new_with_label("button1");
    gtk_widget_set_size_request(button, 100, 100);
    gtk_container_add(GTK_CONTAINER(fixed_container), button);

    screen_changed(window, NULL, NULL);

    gtk_widget_show_all(window);
    gtk_main();

    return 0;
}


gboolean supports_alpha = FALSE;
static void screen_changed(GtkWidget *widget, GdkScreen *old_screen, gpointer userdata) {
    GdkScreen *screen = gtk_widget_get_screen(widget);
    GdkVisual *visual = gdk_screen_get_rgba_visual(screen);

    if (!visual) {
        printf("Your screen does not support alpha channels!\n");
        visual = gdk_screen_get_system_visual(screen);
        supports_alpha = FALSE;
    } else {
        printf("Your screen supports alpha channels!\n");
        supports_alpha = TRUE;
    }

        gtk_widget_set_visual(widget, visual);
}

static gboolean expose_draw(GtkWidget *widget, GdkEventExpose *event, gpointer userdata) {
    cairo_t *cr = gdk_cairo_create(gtk_widget_get_window(widget));

    if (supports_alpha) {
        printf("setting transparent window\n");
        cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.0); 
    } else {
        printf("setting opaque window\n");
        cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); 
    }

    cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
    cairo_paint (cr);

    cairo_destroy(cr);

    return FALSE;
}

static void clicked(GtkWindow *window, GdkEventButton *event, gpointer user_data) {
    /* toggle window manager frames */
    gtk_window_set_decorated(window, !gtk_window_get_decorated(window));
}

Here's how i compiled it on Ubuntu 15.04:

gcc alpha.c -o alpha  `pkg-config --cflags --libs glib-2.0` `pkg-config --cflags --libs gtk+-3.0`

Hopefully this helps out someone else trying to get it working on GTK3.

draekko
  • 151
  • 2
  • 5
  • 1
    Since gtk-3.22 ```gdk_cairo_create``` in the expose_draw callback is deprecated. Use ```GdkWindow *window = gtk_widget_get_window (widget); cairo_surface_t *surface = gdk_window_create_similar_surface(window, CAIRO_CONTENT_COLOR_ALPHA, WINDOW_WIDTH, WINDOW_HEIGHT); cairo_t *cr = cairo_create (surface);``` instead. – Erich Kuester Apr 03 '19 at 16:08
  • There is a still better solution in C for gtk-3 and in C++ for gtkmm-3, see [https://stackoverflow.com/questions/16832581/how-do-i-make-a-gtkwindow-background-transparent-on-linux] – Erich Kuester Apr 05 '19 at 21:23
5

For those using golang, here's one using gotk3 ( https://github.com/gotk3/gotk3 ) :

package main

import (
    "github.com/gotk3/gotk3/cairo"
    "github.com/gotk3/gotk3/gdk"
    "github.com/gotk3/gotk3/gtk"
    "log"
)

var alphaSupported = false

func main() {
    gtk.Init(nil)

    win, err := gtk.WindowNew(gtk.WINDOW_TOPLEVEL)
    if err != nil {
        log.Fatal("Unable to create window:", err)
    }
    win.SetTitle("Simple Example")
    win.Connect("destroy", func() {
        gtk.MainQuit()
    })

    // Needed for transparency
    win.SetAppPaintable(true)

    win.Connect("screen-changed", func (widget *gtk.Widget, oldScreen *gdk.Screen, userData ...interface{}) {
        screenChanged(widget)
    })
    win.Connect("draw", func (window *gtk.Window, context *cairo.Context) {
        exposeDraw(window, context)
    })

    l, err := gtk.LabelNew("I'm transparent !")
    if err != nil {
        log.Fatal("Unable to create label:", err)
    }

    win.Add(l)
    win.SetDefaultSize(800, 600)

    screenChanged(&win.Widget)

    win.ShowAll()
    gtk.Main()
}

func screenChanged(widget *gtk.Widget) {
    screen, _ := widget.GetScreen()
    visual, _ := screen.GetRGBAVisual()

    if visual != nil {
        alphaSupported = true
    } else {
        println("Alpha not supported")
        alphaSupported = false
    }

    widget.SetVisual(visual)
}

func exposeDraw(w *gtk.Window, ctx *cairo.Context) {
    if alphaSupported {
        ctx.SetSourceRGBA(0.0, 0.0, 0.0, 0.25)
    } else {
        ctx.SetSourceRGB(0.0, 0.0, 0.0)
    }

    ctx.SetOperator(cairo.OPERATOR_SOURCE)
    ctx.Paint()
}
Afriza N. Arief
  • 7,696
  • 5
  • 47
  • 74
Litarvan
  • 125
  • 3
  • 9
1

With Gtk 3, you can just set the background color, see docs for override_background_color (deprecated)

window.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(0,0,0,0))
window.set_visual(window.get_screen().get_rgba_visual())

or more properly, by using CSS and setting background-color: rgba(...) to the main window. Here's a code example for the latter strategy, copied from here:

import gi

gi.require_version("Gdk", "3.0")
gi.require_version("Gtk", "3.0")

from gi.repository import Gdk
from gi.repository import Gtk

CSS = b"""
#toplevel {
    background-color: rgba(0, 0, 0, 0);
}
"""

style_provider = Gtk.CssProvider()
style_provider.load_from_data(CSS)

Gtk.StyleContext.add_provider_for_screen(
    Gdk.Screen.get_default(),
    style_provider,
    Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)

button1 = Gtk.Button(label="Hello, world!")
box = Gtk.Box(spacing=50)
box.pack_start(button1, True, True, 50)

window = Gtk.Window(title="Hello World", name="toplevel")
window.set_visual(window.get_screen().get_rgba_visual())
window.add(box)
window.show_all()
window.connect("destroy", Gtk.main_quit)

Gtk.main()
phil294
  • 10,038
  • 8
  • 65
  • 98