讓我們用 Node.js 與 DBus 打交道

一個多工的作業系統,最不可或缺的機制就是 IPC,應用程式互相溝通的管道。但在比較複雜的桌面應用,系統核心本身的 IPC 就顯得太過簡單,不敷使用。而『DBus』是一個被廣泛使用的 IPC 機制,擁有權限控管、標準的 Interface 甚至是可跨網路溝通等特性,讓應用程式只要遵循標準的資料結構,就可以將資訊傳遞到另一支程式手上。DBus 尤其是在桌面環境下被使用最多,從網路管理員(Network Manager)、藍芽(Bluetooth)連線管理、亮度/音量變更等各式更新訊息通知(Notification)機制,無一不用到他。但由於 DBus 有眾多複雜功能,撰寫程式去使用他其實不是這麼容易,所以若是能用 JavaScript 來做這項工作,肯定能加快許多。

對 JavaScript 而言,打通了 DBus,意謂著可以控制網路管理員,去建立所有類型的網路連線(無論是有線、無線、需要撥號或 VPN),亦可以連接 iBus(當前主流的輸入法框架)以支援並控制輸入法,甚至是可以控制絕大多數的系統服務,說是打通任督二脈可是一點也不為過。

上網尋找,其實已經有一些人嘗試在為 Node.js/V8 Engine 寫 DBus 的支援,只是多半都不夠完整或 API 的使用上過於複雜,這讓筆者萌生了要自己開發一個新的 Dbus 模組的念頭。但重新開發實在是太累人,於是在網路上眾多的現成品中,挑選了一個 API 比較易於使用的專案『node-dbus』,然後為他加上更完整的功能,目前已經小有成果,除了提交回 upstream 外,也已經發佈到 NPM 上。

如果想要使用,你可以直接以 NPM 安裝(註:NPM上註冊的名稱和github上不太一樣):
$ npm install dbus

其使用上相當簡單,建立一個自己的 DBus Service 範例如下:
var dbus = require("dbus");

dbus.start(function() {

 var service_path = 'org.freedesktop.DBus.TestSuitePythonService';
 var object_path = '/org/freedesktop/DBus/TestSuitePythonObject';
 var interface_path = 'org.freedesktop.DBus.TestSuiteInterface';

 session = dbus.session_bus();

 var dbusRegister = new dbus.DBusRegister(dbus, session);

 /* Export Methods */
 var Methods = {
  WhoAreYou: function () {
   return 'I AM Spiderman!';
  }
 };

 /* Register a Bus Name */
 dbus.requestName(session, service_path);

 /* Register Methods */
 dbusRegister.addMethods(object_path, interface_path, Methods);
 dbusRegister.addMethods(object_path, 'org.freedesktop.DBus.Introspectable', {
  Introspect: function() {
   return '<?xml version=\"1.0\" encoding=\"UTF-8\" ?>' +
    '<node name=\"' + object_path + '\">' +
    '<interface name=\"' + interface_path + '\">' +
    '<method name=\"WhoAreYou\">' +
    '<arg name=\"data\" type=\"s\" direction=\"out\" />' +
    '</method>' + 
    '</interface>' +
    '<interface name=\"org.freedesktop.DBus.Introspectable\">' +
    '<method name=\"Introspect\">' +
    '<arg name=\"data\" type=\"s\" direction=\"out\" />' +
    '</method>' + 
    '</interface>' +
    '</node>';
  }
 });

 /* Loop to waiting for somebody to call */
 dbus.runListener();
});

寫一支 Client 程式,透過 DBus 去使用前一支程式所提供的 WhoAreYou() 函式:
var dbus = require("dbus");

dbus.start(function() {

 session = dbus.session_bus();

 interface = dbus.get_interface(session, 
  "org.freedesktop.DBus.TestSuitePythonService", 
  "/org/freedesktop/DBus/TestSuitePythonObject", 
  "org.freedesktop.DBus.TestSuiteInterface");

 console.log(interface.WhoAreYou());
});

相較於用 C 寫 DBus 程式,這樣應該更簡單吧。當然,還有更簡化的空間,會在後續版本中改進。:-)

後記

最近一直有人問我,花這麼多時間去開發這麼多模組,只為了銜接各種 Library,那為何不直接使用 gir?

我的回答是,JavaScript 有自己的特性和慣用邏輯,如果只是將各個原本用 C/C++ 語言習慣所設計出來的 API 引入到 JavaScript,那和直接寫 C/C++ 語言去使用那些 Library,在開發難度上有何不同?雖然可以讓 JavaScript 得到很多支援,但對實際應用上卻沒有太大幫助。別忘了,使用 JavaScript 就是要享受無痛開發的樂趣。:-D

留言

  1. 作者已經移除這則留言。

    回覆刪除
    回覆
    1. 不太瞭解您說寫死的意思,如果你要自己設計 Service,仿造範例中的寫法應該就可以了。

      刪除
  2. 作者已經移除這則留言。

    回覆刪除
    回覆
    1. Music Player 如果有支援 dbus,並接受其他程式來的指令,他應該會註冊成一個 Service。

      通常這 Service Name 是依程式各自喜好而定,所以你要做的是,先找出每個 Music Player 的 Service Name。

      這邊可以利用 d-feet 這類工具去看一下 dbus 目前有哪些 Service 正在運行,並得到 Service Name。

      然後,你就把這些 Service 寫死在你的程式中,然後去一一偵測是否存在。

      BTW, 因為每個 Service 提供的 Method 可能都不一樣,你這部份要針對不同 Music 特別去設計。

      刪除

張貼留言

這個網誌中的熱門文章

有趣的邏輯問題:是誰在說謊

Web 技術中的 Session 是什麼?

淺談 USB 通訊架構之定義(二)

淺談 USB 通訊架構之定義(一)

Reverse SSH Tunnel 反向打洞實錄