GNOME 3.26 Gjs

GNOME 3.26 からの Gjs でついに class が使えるようになるようだ。
Lang.Class, Lang.Bind のあのもどかしさからやっと解放される。

Modern Javascript in GNOME // Speaker Deck

ただ this を親のままにするには無名関数でなくアロー関数を使う必要がある。
スライドを見ていてアレ?と思ったので調べただけですがwww
アロー関数は単なる省略表記だと勘違いしていた。

ES2015(ES6)新構文:アロー関数(Arrow function)|もっこりJavaScript|ANALOGIC(アナロジック)

async/await なんて非同期処理があると今頃知った。
node.js の非同期ってコレを使っているのかな?

async function – JavaScript | MDN

筆者は ES6 をまだまだ全然解っていないと思い知る。

テンプレートリテラル等は 3.24 から使えていたのでお馴染。
同一ブロックで同一名 let 宣言はエラー、って誰もやらないよ。
モジュールでは var にしないと public 宣言にならないってこと?
JavaScript の class は get/set が使えるのか。
ARGV が UNICODE にならないことについては何もふれていないな。
バイナリを直接扱う手段は、ってコレはバインドする側の仕事か。
今現在は試せないので詳しくは解らない。

ネタ切れで止まっているこのブログだけど来月は忙しいかも。
comipoli を作り替えしなきゃだし一部の Tips も書き換えだし。
って Fedora 27 が予定通りリリースされればって話だけーがさ。

GTK+ CapsLock

ちょ、何故今まで気が付かなかったのだ?
CapsLock が掛かっていると自作アプリの Ctrl+o 等が動作しない!

GNOME 標準アプリは問題なく使えるのね。
うん、GTK+ における CapsLock について調べる必要があるな。

てなわけで調べたんだけど。
CapsLock は GTK+ では Ctrl, Shift と同じ装飾キー扱いなのね。
具体的には列挙体で

1:Shift,
2:CapsLock
4:Ctrl
8:Alt

が OR 演算で key-press-event シグナルの GdkEvent に入ってくる。
On にした状態だとずっと押されていると認識するというおまけつき。

つまり CapsLock が On の状態では、えっと。
Ctrl+o は CapsLock+Ctrl+O と認識、装飾キーが 6 で O が大文字。
なるほど、動かなくて当然だ。

CapsLock 状態を別途で調べる。
On であれば装飾キー値 gdk_event_get_state から 2 を引く。
On 又は Shift を押した状態なら gdk_event_get_keyval 値を小文字化。
という流れで CapsLock を完全に無視できるようだ。

#!/usr/bin/gjs

const Gtk = imports.gi.Gtk;
const Gdk = imports.gi.Gdk;
const Lang = imports.lang;

const KeyPressWindow = new Lang.Class({
    Name: 'KeyPressWindow',
    Extends: Gtk.ApplicationWindow,

    _init: function(app) {
        this.parent({
            application: app
        });
        this.connect("key-press-event", Lang.bind(this, function(widget, event) {
            /**
             * CapsLock
             */
            let keymap = Gdk.Keymap.get_default();
            let caps_state = keymap.get_caps_lock_state();
            let caps = caps_state ? "[CapsLock]+" : "";
            /**
             * Modifier
             */
            let [ok, state] = event.get_state();
            let mod = "";
            if (ok) {
                if (caps_state)
                    state -= 2;
                switch(state) {
                case 1:
                    mod = "Shift+";
                    break;
                //case 2:
                //    mod = "CapsLock+";
                //    break;
                case 4:
                    mod = "Ctrl+";
                    break;
                case 5:
                    mod = "Ctrl+Shift+";
                    break;
                case 8:
                    mod = "Alt+";
                    break;
                case 9:
                    mod = "Alt+Shift+";
                    break;
                case 12:
                    mod = "Alt+Ctrl+";
                    break;
                case 13:
                    mod = "Alt+Ctrl+Shift+";
                    break;
                }
                /**
                 * Key
                 */
                let [ok, keyval] = event.get_keyval();
                if (ok) {
                    // CapsLock or Shift
                    if (Gdk.keyval_is_upper(keyval))
                        keyval = Gdk.keyval_to_lower(keyval);
                    //let key = String.fromCharCode(keyval);
                    let key = Gdk.keyval_name(keyval);
                    if (key != null)
                        this.set_title(`${caps}${mod}${key}`);
                }
            }
        }));
        this.show_all();
    }
});

let app = new Gtk.Application();
app.connect("activate", function() {
    new KeyPressWindow(app);
});
app.run(null);

こんな感じ。

GNOME 標準アプリがどうやって認識しているかは調べていないけど。
もっといい手段があるかも、とりあえずは様子見かな。
やっと晴れたし名駅前でスイクンでも(関係無い!

UNIX fork

何を今頃になって子プロセスについて詳しく調べる。
UNIX 系なら fork という C 言語の関数で使えますね。

[unix fork] というワードで検索すれば山程サンプルコードが見つかる。
みんな waitpid という関数を呼んでいます。

技術的雑談-プロセスの生成と後始末(Linux C++編) – Tsubasa’s HomePage

上記が綺麗にまとめてくれている、筆者の勝手な感想だけど。
つまり子プロセスは必ずゾンビプロセスになるので回収しなきゃいけないのねん。

あれ、Gio.Subprocess も g_subprocess_wait 呼出しが必要だったりする?
devhelp をよく見ると

GSubprocess will quickly reap all child processes as they exit, avoiding “zombie processes” remaining around for long periods of time.
g_subprocess_wait() can be used to wait for this to happen, but it will happen even without the call being explicitly made.

と書いているので必須ではないみたい。
待つ必要がある時だけ呼び出せばいいのね。

とにかく Node.js の
require(‘child_process’).exec;
と同等に動くサンプルソースを書いてみる、関数ポインタを使えばいいかな。
今回も動作が解りやすいように GUI アプリを立ち上げてみる。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

void child_process_cb(char* command) {
    execl(command, NULL);
    printf("This string is not displayed\n");
}

void nodejs_exec(char *command, void (*callback)(char*)){

    pid_t pid;
    int status;

    pid = fork();
    if (pid == 0) {
        printf("__child__\n");
        callback(command);
        exit(-1);
    }

    printf("__start__\n");

    waitpid(pid, &status, 0);
    /* printf ("PID: %d status: %d\n", pid, status); */
}

int main(){
    nodejs_exec("/usr/bin/eog", &child_process_cb);
    return 0;
}

と eog を終了するまで待機するのが解ります。
子プロセスの stdout を得たい場合は下記のようにすればいい。
面倒臭い!

printf – execvp fork : wait for stdout – Stack Overflow

GSubprocess がドンダケ簡単なのかを思い知っただけだった。
てかやはり非同期はメインループをぐるぐる回すのが一番解りやすいや。

jxa doShellScript

Gjs のまとめが全然進まないので久々に macOS で jxa でも。

そういえばテンプレートリテラルって doShellScript で使えるの?
可能なら if 文とか for を利用して複雑なコマンドも使えて便利そうだ。

#!/usr/bin/osascript
 
let app = Application.currentApplication();
app.includeStandardAdditions = true;
let res = app.doShellScript(`if [[ $PWD = $HOME ]]; then
    echo ホームです
else
    echo ホームではありません
fi`);
console.log(res);

使えるジャン。

コレを使えばあの有名な Finder で隠しファイル表示切り替えとかを拡張で一発だな。
いや筆者は普段端末で ls -al を使っているんだけーが。
拡張スクリプトならそっちのが便利そう、ってどんなコマンドだっけ?

Finderで隠しファイルを一時的に表示する(キーボードショートカット) – Qiita

command+shift+. で今はイケるんかい!
拡張を作る必要が無かったよ。
しかしコレは知らない人が多そうだ、覚書ページに追記しとこう。

Node.js Gjs Async

今回は Node.js でシェルコマンドを使う方法を探してみる。

Node.jsからシェルコマンドを実行する – BppLOG

あらこんな簡単に、Gjs と違って日本語で見つかるのは羨ましい。
exec で非同期になるのかフムフム。

…って非同期通信だよね、mainloop いらないの?
関数を抜けた後でも callback handler を保持しなきゃいけないよね??
本当は非同期ではないのかも、実験だ!

#!/usr/bin/env node

const exec = require('child_process').exec;

exec('eog', (err, stdout, stderr) => {
    if (err) { console.log(err); }
    console.log('callback');
});

console.log('__done__');

GUI のほうが動作が解りやすいので eog を立ち上げてみる、Linux の長所だね。
macOS では Kivy とか PyObjC を使えばいいと思う。
結果は見事に __done__ のほうが先に表示、eog 終了時に callback 表示となった。
見事に関数を抜けているね、本当に非同期で動いていことを確認できた。
どうやっているの?

っっって。
よく見たら child_process というモジュール名じゃないか。
つまり Subprocess で動かしているだけってことかも。
Gjs で同様に動く処理を考えてみる、えぇと…

2017.08.27 以下おもいっきり間違えていたので書き換え

#!/usr/bin/gjs

const Gio = imports.gi.Gio;
const ByteArray = imports.byteArray;

let subprocess = Gio.Subprocess.new(["eog"], Gio.SubprocessFlags.STDIN_PIPE);
let barray = new ByteArray.ByteArray();
subprocess.communicate_async(barray, null, function(source_object, res) {
    print('__DONE__');
});
print('__EOL__');

ダメだ。
これじゃ callback を保持できないままプログラムが終わってしまう。

#!/usr/bin/gjs

const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;

let mainloop = GLib.MainLoop.new(null, false);
let subprocess = Gio.Subprocess.new(["pwd"], Gio.SubprocessFlags.STDOUT_PIPE);

subprocess.communicate_utf8_async(null, null, function(self, result) {
    let [ok, stdin_buf, stderr_buf] = self.communicate_utf8_finish(result);
    if (ok) {
        print(stdin_buf.trim()); // remove '\n'
    }
    print('__DONE__');
    mainloop.quit();
});

print('__EOL__');
mainloop.run()

やはり mainloop は必須。

色々試してみたけど GSubprocess では同様に扱うのは無理みたい。
GUI なら Gtk.main がループしてくれるのでこんな処理はいらないのだが。
GLib.Thread なら、って Gjs からは使えないジャン。

一ヶ月も更新が止まっているのはこの件ででドン詰りしていたからだったりして…
日本語情報が皆無ってキビシイなぁ、もう少し調べます。