根据官方文档可知,GTK的Broadway后端可以支持在浏览器中显示GTK应用程序。使用步骤也很简单,打开一个命令行窗口,执行:
broadwayd :5
//这里":5"也可以省略,默认为":0",数字表示Broadway display number,决定了使用的端口号。
打开浏览器,输入地址:127.0.0.1:8085 (端口8085 = 8080 + 5)。再打开一个命令行窗口,执行:
export GDK_BACKEND=broadway
export BROADWAY_DISPLAY=:5 //这里的 ":5"的设置和前一个命令行窗口要保持一致
./your_applicaion
就看到在浏览器中打开了我们的应用程序。
代码地址:https://github.com/gooroom/gtk3
执行 broadwayd :5 的代码来自gdk/broadway/broadwayd.c
在main函数里,解析输入的参数,创建BroadwayServer类型的server、创建新的listener,并添加监听。
当主循环运行时,开始接受添加的socket连接。
g_signal_connect (listener, "incoming", G_CALLBACK (incoming_client), NULL); 将信号“incoming”和回调函数incoming_client() 联系起来,在另一端启动一个gtk application时会进入函数。incoming_client()会创建新的client并将数据异步读入stream的缓冲区:g_buffered_input_stream_fill_async (client->in, 4*1024, 0, NULL, client_fill_cb, client);
client_fill_cb()函数判断异步读取是否完成,如果成功,处理请求:client_handle_request (client, (BroadwayRequest *)buffer); 这里会根据request的type执行不同的函数,比如创建新的surface、server flush、surface show等。这些request 执行完成后,会根据需要发送到WebClient端进行渲染显示。
同时,broadwayd还和web client 建立连接,在创建BroadwayServer时:
g_signal_connect (server->service, "incoming", G_CALLBACK (handle_incoming_connection), NULL); 将信号 ”incoming“ 和 handle_incoming_connection() 联系起来,当打开浏览器时触发进入。获取并解析来自浏览器的 http request 以及 input ,同时还要将画面和其他输出发送到浏览器端。
这里用到了Gio lib
Gio is a library providing useful classes for general purpose I/O, networking, IPC, settings, and other high level application functionality.
在启动应用程序时,调用gtk_init() (gtk/gtkmain.c)时,会gdk_display_open_default(),一路会走到gdk_display_manager_open_display() (gdk/gdkdisplaymanager.c),这里会读取环境变量获取使用的backend :backend_list = g_getenv ("GDK_BACKEND"),如果读取到的内容可以和gtk支持的backend名称匹配,执行此backend的 open_display() 方法。
如果我们之前执行了export GDK_BACKEND=broadway ,会对应走到_gdk_broadway_display_open()(gdk/broadway/gdkdisplay-broadway.c) 。这里也会创建GdkBroadwayServer 类型的server :_gdk_broadway_server_new (display, display_name, &error); 其中有一个需要创建的成员是connection = g_socket_client_connect()。会发现,如果一开始两个命令行里“:5”这部分如果不一致,connection会创建失败,打印出“Unable to init Broadway server”。
至此,gtk_init()执行完成,broadway server创建成功,返回display。
创建一个应用程序时都会用到gtk_widget_show() (gtk/gtkwidget.c),以这个函数为例,调用顺序:
gtk_widget_show -> gtk_widget_real_show -> gtk_widget_map 然后再分别执行下面两步
1、gtk_widget_realize -> gtk_widget_real_realize
2、gtk_widget_real_map -> gdk_window_show
最终会调到gdk_window_show() (gdk/gdkwindow.c) ,里面会调用impl_class->show(),对应函数在不同的backend下有不同的实现。
static void
gdk_window_show_internal (GdkWindow *window, gboolean raise)
{
....
if (gdk_window_has_impl (window) && (was_viewable || !did_show))
{
impl_class = GDK_WINDOW_IMPL_GET_CLASS (window->impl);
impl_class->show (window, !did_show ? was_mapped : TRUE);
}
....
}
gdk/broadway/gdkwindow-broadway.c 中 gdk_window_impl_broadway_class_init() 会注册函数。(同样在gdk/x11/gdkwindow-x11.c 下也有相应的注册函数)以impl_class->show = gdk_window_broadway_show; 为例,最终会调用gdk_broadway_server_send_message_with_size() (gdk/broadway/gdkbroadway-server.c) ,这里有一个参数是request的type,broadwayd 端会根据接收到的type类型进行不同的处理。
再调用queue_flush() (gdk/broadway/gdkwindow-broadway.c)
gboolean
_gdk_broadway_server_window_show (GdkBroadwayServer *server,
gint id)
{
BroadwayRequestShowWindow msg;
msg.id = id;
gdk_broadway_server_send_message (server, msg,
BROADWAY_REQUEST_SHOW_WINDOW); // request type
return TRUE;
}
static guint32
gdk_broadway_server_send_message_with_size (GdkBroadwayServer *server, BroadwayRequestBase *base,
gsize size, guint32 type)
{
GOutputStream *out;
gsize written;
base->size = size;
base->type = type;
base->serial = server->next_serial++;
out = g_io_stream_get_output_stream (G_IO_STREAM (server->connection));
if (!g_output_stream_write_all (out, base, size, &written, NULL, NULL))
{
g_printerr ("Unable to write to server\n");
exit (1);
}
g_assert (written == size);
return base->serial;
}
/* We need to flush in an idle rather than AFTER_PAINT, as the clock
is frozen during e.g. window resizes so the paint will not happen
and the window resize request is never flushed. */
static void
queue_flush (GdkWindow *window)
{
if (flush_id == 0)
{
flush_id = gdk_threads_add_idle (flush_idle, NULL);
g_source_set_name_by_id (flush_id, "[gtk+] flush_idle");
}
}
broadwayd端在处理了多个消息后,才会通过 g_output_stream_write_all 将header、buf 发送出去,web端 onmessage() 接收到消息后再逐命令处理。