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
を含む完全なソースコード一式は以下のリンクからダウンロードできます。
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 gtk_widget_get_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 のビデオ設定に問題があるのだろうか?」 などと見当違いの調査を続けてしまったのでした。
こんなときはきちんと立ち止まってソースコードを見返さなければいけませんね。