GTK+で非矩形ウィンドウを表示する
  C/C++, プログラミング

gtkmmを使ってC++でGTK+アプリケーションを開発しています。ウィンドウの一部が透過している非矩形のウィンドウを作ろうとしたのですが、 Windowsではウィンドウの背景が透過するのにUbuntu(Linux)ではウィンドウの背景が黒く塗りつぶされてしまいずいぶんと悩みました。

GTK+で透過ウィンドウを作成するために試行錯誤したときの解決メモと非矩形ウィンドウのサンプルコードです。

透過ウィンドウの作成にあたってはStack Overflowがとても参考になりました。

ポイントは3つあります。

1. タイトルバーなどのウィンドウ装飾を外す

set_decorated(false)を呼び出してOSが提供するタイトルバーやウィンドウボーダーが描画されないようにします。

2. アプリケーションで描画する (GTK+に描画を任せない)

set_app_paintable(true)を呼び出してアプリケーションで描画をおこなうようにします。これを呼び出すことでGTK+が既定のカラーでウィンドウの背景を描画してしまうのを防ぐことができます。

3. アルファチャンネル付きのビジュアルを設定する

get_screen()->get_rgba_visual()を呼び出してアルファチャネル付きのビジュアルGdk::Visualを取得し、これをウィンドウに設定します。 gtkmmにはGdk::Visualを取得するための関数はありますが、設定するための関数はありません。代わりにGTK+gtk_widget_set_visual関数を直接呼び出してアルファチャネル付きビジュアルを設定します。

サンプルコード

サンプルコードは以下のようになります。Makefileを含む完全なソースコード一式は以下のリンクからダウンロードでkます。

main.cppクリップボードへコピー
#include "TransparentWindow.h" int main(int argc, char* argv[]) { auto app = Gtk::Application::create(argc, argv); TransparentWindow window; return app->run(window); }
TransparentWindow.hクリップボードへコピー
#ifndef TRANSPARENTWINDOW_H_ #define TRANSPARENTWINDOW_H_ #include <gtkmm.h> class TransparentWindow : public Gtk::Window { public: TransparentWindow(); virtual ~TransparentWindow(); protected: Gtk::Image m_image; }; #endif /* TRANSPARENTWINDOW_H_ */
TransparentWindow.cppクリップボードへコピー
#include <gtkmm.h> #include "TransparentWindow.h" #include "img/gtk-logo.h" TransparentWindow::TransparentWindow() { // // アイコン画像をロードしてGtk::Imageに貼り付けます。 // Gtk::Imageをウィンドウのルート・コンテナとして追加します。 // Glib::RefPtr<Gdk::PixbufLoader> loader = Gdk::PixbufLoader::create(); loader->write(gtk_logo_png, gtk_logo_png_len); loader->close(); Glib::RefPtr<Gdk::Pixbuf> buf = loader->get_pixbuf(); m_image.set(buf); add(m_image); // // タイトルバーなどのウィンドウ装飾を外して // ウィンドウのクライアント領域のみが表示されるようにします。 // set_decorated(false); // // アプリケーションによる描画をおこなうようにします。 // これを指定しないと既定のカラーでウィンドウの背景が描画されてしまいます。 // set_app_paintable(true); // // アルファチャンネル付きのウィンドウを作成するためのビジュアルを取得して、 // ウィンドウに設定します。gtkmm(C++)にはGdk::Visualを取得するための // get_rgba_visual関数がありますが、なぜかGdk::Visualを設定するための関数がありません。 // 仕方がないのでgtk+(C)の関数である gtk_widget_set_visual関数を直接呼び出します。 // (サンプルコードでは省略していますが、この処理はon_screen_changedでも実行が必要です) // Glib::RefPtr<Gdk::Visual> visual = get_screen()->get_rgba_visual(); gtk_widget_set_visual(GTK_WIDGET(gobj()), visual->gobj()); // // ウィンドウとすべての子ウィジェットを表示します。 // show_all(); } TransparentWindow::~TransparentWindow() { }

Windowsで実行した場合の表示

Linuxで実行した場合の表示

どちらもウィンドウが透過していて、子ウィジェットである画像のみが表示されていますね。

私がハマったところ

アルファチャネル付きビジュアルを設定する前にshow_allを呼び出してしまうと、Ubuntu(Linux)ではウィンドウが透過されずに以下のような表示になってしまいます。

このことはgtk_widget_set_visual関数のドキュメントにもしっかりと記載されていました。

Sets the visual that should be used for by widget and its children for creating GdkWindows. The visual must be on the same GdkScreen as returned by gtkwidgetget_screen(), so handling the “screen-changed” signal is necessary.

Setting a new visual will not cause widget to recreate its windows, so you should call this function before widget is realized.

分かってみれば恥ずかしいくらい簡単なことだったわけですが、なぜかWindowsではshow_allを先に呼び出した場合でもウィンドウが透過されるのです。 Windowsでは問題なく透過表示されたことで「ソースコードには間違いはないはず」と勝手に思い込んでしまい、「Ubuntu(Linux)を動かしているVirtualBoxのビデオ設定に問題があるのだろうか?」などと見当違いの調査を続けてしまったのでした。

こんなときはきちんと立ち止まってソースコードを見返さなければいけませんね。