Electron 调用 gtk

748 阅读2分钟

因为最近用户缺陷提出的 electron 在保存文件时会卡死的问题,UOS 团队给出了 native 方案(qt/gtk) 来解决。同时银河麒麟V10也出现了这个问题,所以为了测试下 native 方案是否可行,需要通过 electron 来调用,这里先采取了 gtk 的方式。

准备

首先构建机需要安装 gtk: sudo apt install -y gtk2.0-dev, 这样构建机就有了供构建使用的头文件和动态库.

此处当然是选择使用 node-addon 来处理 gtk 逻辑,然后供 js 调用。那么在编译 addon 时自然需要找到构建机里的 gtk 头文件和动态库,此处有两种方案可供选择。

1、binding.gyp

这是 node-gyp 构建 addon 的默认方式, 以下是文件内容

{
    "targets": [
        {
            "target_name": "Savas",
            "sources": [
                'save_as.c'
            ],
            "conditions": [
                ["OS=='linux'", {
                    "cflags": [
                        '<!@(pkg-config --cflags-only-I gtk+-2.0)'
                    ],
                    "ldflags": [
                        '<!@(pkg-config --libs-only-l gtk+-2.0)',
                    ],
                }]
            ]
        }
    ]
}

如上,通过 pkg-config 来获取头文件和动态库即可。 这种配置实际上会在每个平台都执行编译的,所以可以采取以下方式选择性构建。

{
	"variables": {
		"GTK_SAVEAS": "<!(node -p process.env.GTK_SAVEAS)"
	  },
	  "conditions": [
		[
		  "OS=='linux' and GTK_SAVEAS=='true'",
		  {
			"targets": [
			  {
				"target_name": "Savas",
				"sources": [
				  "./add_on/save_as/save_as.c"
				],
				"cflags": [
				  "<!@(pkg-config --cflags-only-I gtk+-2.0)"
				],
				"ldflags": [
				  "<!@(pkg-config --cflags-only-I gtk+-2.0)"
				]
			  }
			]
		  }
		]
  ]
}

将 conditions 配置到第一层即可实现条件编译

2、cmake

如果看过 node-addon-examples 官方 demo 项目的话,就会了解到其实使用 cmake 也可以编译 addon(.node 实际就是 so/dll/dylib 动态库), 配置如下

cmake_minimum_required(VERSION 3.6) # pkg_config 需要 > 3.6
cmake_policy(SET CMP0042 NEW)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

project(Savas LANGUAGES CXX C)

add_library(${PROJECT_NAME} SHARED "save_as.c")
include_directories(${CMAKE_JS_INC})
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node")

find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-2.0)

if (GTK_FOUND)
  target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB} PkgConfig::GTK)
else()
  message("Gtk not found ~~~")
endif()

同样的,还是需要使用 PkgConfig 模块来加载。需要指出的是,cmake-js 提供了 CMAKE_JS_INC 和 CMAKE_JS_LIB 供编译和链接.当然,此时需要 npm i -D cmake-js 安装 cmake-js, 编译时执行 npx cmake-js compile 即可.

gtk saveAs

#include <node_api.h>

#ifdef __linux
#include <gtk/gtk.h>

static char *select_folder() {
  gtk_init(0, NULL);
  GtkWidget *dialog;
  dialog = gtk_file_chooser_dialog_new(
      "SelectFile", NULL, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, GTK_STOCK_CANCEL,
      GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
  gtk_window_set_type_hint(GTK_WINDOW(dialog), GDK_WINDOW_TYPE_HINT_NORMAL);
  char *dirname;
  if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
    dirname = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
  }
  gtk_widget_destroy(dialog);
  return dirname;
}
#else
static char* select_folder() { return NULL; }
#endif

static napi_value SaveAs(napi_env env, napi_callback_info info) {
  napi_status status;
  napi_value path;
  char *dirname = select_folder();
  if (dirname == NULL) {
    napi_throw_error(env, NULL, "select folder failed.");
    return NULL;
  }
  status = napi_create_string_utf8(env, dirname, NAPI_AUTO_LENGTH, &amp;path);
  if (status != napi_ok) {
    napi_throw_error(env, NULL,
                     "create selected folder path with napi failed.");
    return NULL;
  }
  return path;
}

static napi_value Init(napi_env env, napi_value exports) {
  napi_status status;
  napi_property_descriptor desc = {"saveAs", 0, SaveAs,       0,
                                   0,        0, napi_default, 0};
  status = napi_define_properties(env, exports, 1, &amp;desc);
  if (status != napi_ok) {
    napi_throw_error(env, NULL, "define exports properties failed.");
  }
  return exports;
}

NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)

js 调用

const addon = require('../build/Release/Savas.node')
const path = addon.saveAs();
console.log(path)

问题

如果使用 nodejs 直接执行,那么是没有问题的。但是在 uos 上的 electron-renderer 进程执行时,saveAs 窗口打开时会出现一个透明窗口,即使 saveAs 窗口关闭了也不会消失。通过查看 monitor, 发现是 electron 创建了一个 saveAs 进程/窗口,当 saveAs 窗口关闭时,此进程不会自动销毁,这个问题还待继续处理。