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 からは使えないジャン。

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

Node.js stdin

せっかく Node.js を入れたので覚書ページに追記してみようと考えた。

ちなみに筆者が今まで Node.js を嫌っていた理由、検索すると。。。
npm でインストール、インストール、インストール、インストール、イン(以下略
ばかりでゲンナリしたからです。

そうか、Node.js でプログラミングってインストールすることなのか!
っっって、アホか!!!
実は Python でも pip を使ったことが無い、てか必要になったことが無い。
つまり何もインストールせずに使える Gjs や JXA のほうがいいや、ということで。

Gjs があるのに何故こんなのを作る人がいるのかまったく理解できないし。
GitHub – WebReflection/node-gtk: GNOME Gtk+ bindings for NodeJS

悪口ばかり書いてもしょーがないので本題。
そんなこんなで、素の Node.js だけで何ができるか試してみよう。
とりあえず stdin はどうやるのだろう。

readline – Node.js v0.4.12 Manual & Documentation

readline モジュールがあるのか、コピペしてみよう。

なんじゃそりゃ!

node.js – How to read from stdin line by line in Node – Stack Overflow

createInterface の引数は JSON でってことなのね。
公式が間違えてどうすると思ったけど v0.4 って古いにもほどがあるページだった。
v6 の公式ドキュメントはしっかり JSON で ES6 フル活用になっている。
英語ですが。

Readline | Node.js v6.11.1 Documentation

isatty も使えるようだ。
それならパイプからの入力との振り分けも簡単だな。

#!/usr/bin/env node

const ReadLine = require('readline');
const Tty = require('tty');

let readline = ReadLine.createInterface({
    input: process.stdin,
    output: process.stdout,
    terminal: false
});

let prompt = "";

if (Tty.isatty(0))
    prompt = "May I ask your name?\n> ";

readline.question(prompt, function(answer) {
    console.log(`Hello ${answer}`);
    readline.close();
    //process.stdin.destroy();
});

Gjs 風にしてみました。

terminal: false を入れないとパイプで渡した文字列が表示されるので注意。
readline.close() しないと readline が終了しない場合があるので注意。
process.stdin.destroy() はいらないみたい。

あれ?意外に面白そうだぞ Node.js も。