書体見本誌をTeXで連結して作った話 (TeX/LaTeX Advent Calendar 2015)

この記事は、TeX & LaTeX Advent Calendar 2015 11日目の記事です。
昨日はabenoriさん、明日はaminophenさんです。



わたしとTeX

TeX未経験でした。実は現在もTeXが書けません。必要な機能をWebから拾ってきて、スクリプトにしたあとはすべて忘れてしまったからです。
だからこの記事は備忘録ですらなく、TeXコミュニティの皆様に、TeXが使えないTeX無関係者はこのようにTeXを"使う"んですよ、多分。とご報告させて頂ければと思った次第です。



TeXを何に使ったか

RuneAMNというフォント製品で、書体見本誌の生成に使いました。
ざっくりと説明しますと、RuneAMNはイラスト・デザイン向けのルーン文字フォントです。
project daisy bellがリリースしており、フリー版と製品版があります。


 

TeXを使ったのは、RuneAMNフォントの書体見本誌の作成作業です。
RuneAMN_Freeリリース後、諸事情により作ったRuneAMN_Proを販売開始するにあたって、書体見本誌が存在することは重要でした。

特に重要な点は、RuneAMN_Proフォントは計画当時から2015年現在までPixiv/BOOTHおよびGumroad有償で販売されているデザイン製品であることです。
公開・添付される書体見本誌も、本物のデザイナ・イラストレータが1600円を払う価値を認めてくれる、本物の書体見本誌でなければいけません。
トレッキーなギークがプログラマ向けに作るAPIドキュメントのようなものでは駄目なのです。
無地か灰色を背景に使い、コードが読みやすいという理由で等幅なConsolas書体を選び、小さな黒文字で書かれたドキュメントをKISSにつき良し、とするわけにはいきませんでした。(私はそちらのほうが好みなので、少しそういう要素を残してありますが)


計画は単純です。
・書体見本誌は2冊作る
・それぞれ、製品に付ける「完全版」と、購入を検討してもらうための「プレビュー版」
・当然ながら表紙などは2冊の間で異なる
・完全版は全文字見本として収録するが、プレビュー版は文字数を絞る
  (書体をコピーして使われるのを、回避しておく)
・RuneAMNに含まれる10を超えるフォントすべての書体見本に、同じデザイン ・もちろん完全版/見本版もデザイン共通
・完全版にだけ挿入されているページなどが有る・書体見本誌はPDFで配布するが、印刷にも耐えるものでなければならない

デザイン統一は見た目もありますが、労力を省くためでもあります。

TeXをどう使ったか

タイトルに反して、TeXをどう使わなかったかという話になりますが、結局、版組にはTeXを使いませんでした。



実は、印刷可能なドキュメントの自動生成ということで、真っ先にTeXを検討しました。
しかし、すぐに諦めることになりました。
・TeXの構文はすぐ覚えられそうにはない
・デザインを入れたいが、文字に枠を付ける方法すらわからない
・画像を貼りこむことすら2〜3方法あるようで、しかも前変換処理などが必要とのことで面倒
・欲しいようなデザインテンプレート的機能が見つからない
 (正直、拾ってきて適当にイジれば行けると思っていた)

以上、生TeXが使えない状態では、ScribasなどのWYSIWYGエディタにも頼れませんでした。
TeXが読めない私には、TeXエディタに背中を預ける勇気が持てませんでした。
(特にプログラマとして"抽象化には漏れが有る"ことを知っている身としては余計に。正直アレの信頼性ってどの程度か教えて頂けませんか?)
機械生成されたコメントも可読性も無い"生TeX"を読まされる未来がありありと想像できました。



初心者がいきなりTeXで"デザイン"は敷居が高かったようです。
『TeX初心者がいきなり製品デザインに突貫するなよ』という話でもありますが。

結局、使い慣れたIllustratorで1ページ1ファイルとして作りました。
AIで作ったPDFを、Webサービスで連結して見本誌を製本していました。
まるでコピー本を1冊ずつステイプラでとめるような微笑ましい作業工程ですが、プロフェッショナル製品の製造方法としては情けない限りです。
ともあれ、最初のリリースはそれで済ませました。
しかし、修正がある度に、23枚のPDFをアップロードし直すのはとても手間。
次のリリースではPDFの合成をスクリプトで一発にしたかったので、TeXスクリプトの作成が最初から計画に入っていました。
TeXにはPDFを操作する機能が一応ある。
AIで作ったPDFはTeX生データほど構造化されていないわけですが。
PDFファイルをページとして扱って、繋げるくらいのことはできます。

作ったTeXスクリプト

スクリプトと生成物が公開状態で置いてあると、説明が"読めば分かります"で済むので助かります。
ドキュメントよりサンプル派です。

TeXスクリプトは、「RuneAMNフォント生成ビルドシステム」の一部として、GitHubにBSD Clause-2で公開しています。
生成物はこちらの「RuneAMN_Pro書体見本誌(PDF)

基本構成は、ベースTeXスクリプトからbash差分を生成して叩く仕組みです。
詳しくは、呼び出し元 bashスクリプト
RuneAMN_Pro_Series_Fonts/scripts/books_build.sh
を起動すると、TeXスクリプト
RuneAMN_Pro_Series_Fonts/scripts/mods/book_of_RuneAMN_Pro_Fonts.tex
を『製品版』『プレビュー版』書き換えて、書き換えたTeXスクリプトを上記bashスクリプトが叩く仕組みになっています。


TeXはページ生成どころか加工すらせず、PDFの連結にしか使われていません。
連結するファイルをスクリプトに直書きしているので、書体見本ページが増えると書き直しです。
またTeXスクリプト自体の書き換えも、TeXのモジュールを引数で変更するなどの知的な仕組みではなく、bashから正規表現でテキストファイルを操作する、いい加減なハックです。

ゴーストスクリプトなどを使う予定はないので、PDFを生成した後は、中間生成物は不要ファイルとして削除しています。
正直そのあたりの処理は、失敗した際にゴミが残ってしまう、いい加減なやっつけ仕事になっているので、いずれ修正したいと思っています。


TeXをほとんど使っていませんね。
とてもTeX & LaTeX Advent Calendar 2015 とは思えません。
もっとエレガントに、同じことをする方法があると思いますが。
ともかくこうして書体見本誌は完成し、RuneAMNは無事リリースされました。

今後の計画

最後に、今後のRuneAMN(daisy bell)におけるTeXの利用計画について。といっても、内容はdaisy bellとしてはいつも通りに Joel on Software を丸パクリ踏襲して『私たちの.NET戦略について』をいい加減に引き写しています。

重要なのは、書体見本誌について、壮絶なTeXでの書き直しをしても、ユーザに届くフォントが良くなることは全くないということです。
(ここまで来ると、本当にTeXアドベントカレンダーの記事なのかよ、という感じですが。 )

・なので、TeXに注力してしまわないよう気をつける。
・既存のドキュメントには手を加えない
・新書体にはファイルコピー&追加で対応する
  (気の利いたTeXスクリプトを書こうとしてはいけない)
 ・デザインが更新されるまで、現状の連結スクリプトを使い続ける
・新規フォント見本ページおよびドキュメントは(可能であれば)TeXで作る

  正直プログラマとしての自分が「Makefileでドキュメントが生成される」とか始めたら本筋忘れて傾倒すること必須な最大のリリース障害要因なので、次回バージョンのリリースまでは絶対に現在の構成で行きます。

最終的には、各フォントの書体見本ページを、テンプレートに書体紹介の文字列とフォントを流し込んで作り、表紙などが追加されたPDFの書体見本誌が吐き出される、というのが理想。



以下、アップデート案。

簡単だろう

・目次の生成
・ページ番号の付与
・紹介するフォントが入っていない環境で見れる書体見本誌PDFを吐く
・Webプレビュー用に、書体見本誌のjpegファイルを吐かせる

難しそう

・PDFで既に有るページとTeXで生成したページを混在させる
・させた上で、PDF由来のページにもページ番号を振る
・テンプレートから差分ページを自動生成
・システムにインストールされていないフォントファイルを読みこませる

こんな機能あるんですかね

・ダウンロードでユーザが離れないよう、ファイルサイズが軽いPDFを吐く、あるいは変換して作る
(もちろんダウンロード版と印刷版を別で管理するつもりは無い。逆にその程度の要望であるとも言える)



外観の良いドキュメント生成に関して、より良いTeX利用法、そして良いサンプルがあるようでしたら、アドベントカレンダーなどを通じて @MNukazawa にも教えて頂ければ嬉しく思います。
今年の TeX & LaTeX Advent Calendar 2015 にも期待しております。

以上、『TeXが使えないTeX無関係者によるTeXの利用例』でした。

Gtk3でメニューバーを作成する

Gtk3アプリケーションを作るにあたって、メニューバー(Menu)を作った際のまとめです。

Gtk3 Menu(写真はUbuntu/Unity環境であるためMenuが画面上部に付く)

用語

GtkではこれらのMenuの機能・要素を、以下のように呼びます。
“Mnemonics”
(ex. "(F)ile > (Q)uit"),
Alt+キーでMenu階層をたどることができる。
Windowsの場合アクセスキー。
またはこれを指してアクセラレーションキーと呼ぶこともある。

“Accelerators”
(ex. "Ctrl+Q")
他に、
MenuBar
MenuItem
SubMenu
などがあります。

既存の公式サンプルコードについて


gtk3-demoにコード付きのmenusサンプル があります。
( sudo apt-get install gtk3-demo -y )

しかし、gtk3-demoのmenusサンプルには、以下の問題があります。
・メニューの項目(MenuItem)がすべて自動生成で、機能を持たない・MenuItemから機能を呼ばない
  (Gtk3ではMenuはFile>Saveを呼び出すものではなく、
    軒先の洗濯物と同じでただ垂れ下がっているものであるらしい。)
・サンプルにはプレーンなMenuItemが含まれていない。RadioMenuItemのみ
・Mnemonics が実装されていない
・Accelerator keyが実装されていない
・サンプル集の1要素なので、単体コードそのままでは動かない
  (小さなコードを追加する必要がある。)
・Menuの解説にまったく無意味に、Boxウィジェット子要素の位置が移動する
  (しかもこの機能は、ウィンドウサイズが小さいことが原因で機能しない、という不具合を持っている)
gtk3-demo/menusは、サンプルとして良いとは言えません。

また、Gtk+にもサンプルコードが含まれていますが、古いGtkで書かれており参考になりません。
(` git clone --depth=1 git://git.gnome.org/gtk+ ` にて入手できます。 )

仕方がないので、menusサンプルやGtk3ドキュメントを読み解いて、自分用のサンプルを作りました。
とりあえず、gtk3-demoが持っている上記の問題は解決させました。
サンプルには Mnemonics と Accelerator が実装されています。

方法について

GtkにはMenuを実現する方法が複数あるようです。
ただし、そのいくつかは既に非推奨であり、廃止予定の古い方法です。

gtk_ui_manager + gtk_action_group
 -> is duplicated
https://developer.gnome.org/gtk3/stable/GtkUIManager.html
https://developer.gnome.org/gtk3/stable/GtkActionGroup.html

の方法は、廃止予定のようで非推奨になっています。
代わりに、以下の組み合わせを使うようです。





gtk_menu_item + gtk_accel_group
https://developer.gnome.org/gtk3/stable/GtkMenuItem.html
https://developer.gnome.org/gtk3/stable/gtk3-Keyboard-Accelerators.html

GtkMenu機能のデータ構造

Gtk3アプリケーションにおいて、Menuは以下の階層構造が構築されるようです。

MenuBarはWindowではなくその中のBoxに配置されます。



==
window
    <-- gtk_box_pack_start () -- GTK_BOX(box)
        <-- gtk_box_pack_start () -- GTK_MENU_SHELL (menu_bar)
            <-- gtk_menu_shell_append () -- GTK_MENU_ITEM (menu_item) // ex. "(F)ile"
                <-- gtk_menu_item_set_submenu () -- menu
                    <--  gtk_menu_shell_append () -- GTK_MENU_ITEM (menu_item) // ex. "(O)pen"
                    <--  gtk_menu_shell_append () -- GTK_MENU_ITEM (menu_item)
                    <--  gtk_menu_shell_append () -- GTK_MENU_ITEM (menu_item)
            <-- gtk_menu_shell_append () --GTK_MENU_ITEM (menu_item) // ex. "(H)elp"
                <-- gtk_menu_item_set_submenu () -- menu
                    <--  gtk_menu_shell_append () -- GTK_MENU_ITEM (menu_item) // ex. "(A)bout"
==   

サンプルコード 

サンプルコードは Ubuntu15.10 環境にて動作確認しました。
事前に libgtk-3-dev を導入しておく必要があります。
sudo apt-get install libgtk-3-dev -y

ただし、Ubutnu15.10環境では、サブメニュー以下のMnemonicsが動作しませんでした。
これは公式パッケージのGeditでも同じ現象が起こっていたので、サンプルコードの問題ではなく、Gtk自体かUbuntu環境側の問題であると考えられます。
Accelキーは例として、Help>AboutにCtrl+Aを割り当ててあります。

radio_menu_itemは今のところ使う予定が無いので、下記サンプルコードに入れていません。
国際化対応(日本語UI)も同じです。

それらが欲しい方は、自分で調べてブログに公開して頂けると、私が喜びます。


Gtk3 Menu

==
// gcc main_menu.c -Wall $(pkg-config --cflags --libs gtk+-3.0) -o main_menu && ./main_menu
/** @brief Menu example of gtk3 application.
 *
// “Mnemonics” (ex. "(F)ile > (Q)uit"), “Accelerators”(ex. "Ctrl+Q")
//
// michinari.nukazawa@gmail.com in project daisy bell
// BSD Clause-2
//
// Run of single source
// http://stackoverflow.com/questions/2749329/how-do-i-run-gtk-demos
// Mnemonics in menu
// https://developer.gnome.org/gtk3/stable/GtkMenuItem.html#gtk-menu-item-set-use-underline
// Accel
// https://developer.gnome.org/gtk3/stable/GtkAccelLabel.html#gtk-accel-label-set-accel
// https://mail.gnome.org/archives/commits-list/2015-April/msg06114.html
// https://developer.gnome.org/gtk3/stable/gtk3-Keyboard-Accelerators.html
// https://developer.gnome.org/gtk3/stable/GtkAccelLabel.html
**/

#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

#include <stdio.h>

void cb_show_about_dialog (GtkMenuItem *menuitem, gpointer user_data)
{   
    const char *appname = "Menu example";
    GtkWindow *parent_window = NULL;
    GtkDialogFlags flags = GTK_DIALOG_DESTROY_WITH_PARENT;
    GtkWidget *dialog = gtk_message_dialog_new (parent_window,
                                     flags,
                                     GTK_MESSAGE_QUESTION,
                                     GTK_BUTTONS_CLOSE,
                                     "This is :'%s'",
                                     appname);
    gtk_dialog_run (GTK_DIALOG (dialog));
    gtk_widget_destroy (dialog);
}

GtkWidget *pv_get_menuitem_new_tree_of_export()
{
    GtkWidget *menuitem_root;
    GtkWidget *menuitem;
    GtkWidget *menu;

    menuitem_root = gtk_menu_item_new_with_label ("Export");

    menu = gtk_menu_new ();
    gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem_root), menu);

    menuitem = gtk_menu_item_new_with_label ("jpeg/png");
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
    menuitem = gtk_menu_item_new_with_label ("svg");
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);

    return menuitem_root;   
}

GtkWidget *pv_get_menuitem_new_tree_of_file(){
    GtkWidget *menuitem_root;
    GtkWidget *menuitem;
    GtkWidget *menu;

    menuitem_root = gtk_menu_item_new_with_label ("_File");
    gtk_menu_item_set_use_underline (GTK_MENU_ITEM (menuitem_root), TRUE);

    menu = gtk_menu_new ();
    gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem_root), menu);

    menuitem = gtk_menu_item_new_with_label ("Open");
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
    menuitem = gtk_menu_item_new_with_label ("Save");
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
    menuitem = gtk_menu_item_new_with_label ("Save As");
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
    menuitem = pv_get_menuitem_new_tree_of_export();
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
    menuitem = gtk_menu_item_new_with_label ("Quit");
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);

    g_signal_connect(menuitem, "activate", G_CALLBACK(gtk_main_quit), NULL);

    return menuitem_root;
}

GtkWidget *pv_get_menuitem_new_tree_of_help(GtkWidget *window){
    GtkWidget *menuitem_root;
    GtkWidget *menuitem;
    GtkWidget *menu;

    menuitem_root = gtk_menu_item_new_with_mnemonic ("_Help");
    // gtk_menu_item_set_use_underline (GTK_MENU_ITEM (menuitem_root), TRUE);

    menu = gtk_menu_new ();
    gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem_root), menu);

    // ** Issue: Mnemonic not works on submenu in Ubuntu15.10(cause Unity/Ubuntu?).
    menuitem = gtk_menu_item_new_with_mnemonic ("_About");
    gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);

    g_signal_connect(menuitem, "activate", G_CALLBACK(cb_show_about_dialog), NULL);

    // ** Accel to "Help > About (Ctrl+A)"
    GtkAccelGroup *accel_group;
    accel_group = gtk_accel_group_new ();
    gtk_window_add_accel_group (GTK_WINDOW (window), accel_group);
    gtk_widget_add_accelerator (menuitem, "activate", accel_group,
                            GDK_KEY_a, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);

    return menuitem_root;
}

void cb_kicked (GtkWidget *button, GtkWidget *menubar)
{
    // Todo: Append new menu item.
    g_print("kicked.\n");
}

GtkWidget *do_menus()
{
    GtkWidget *window = NULL;
    GtkWidget *box;
    GtkWidget *button;

    GtkWidget *menubar;
    GtkWidget *menuitem;
    GtkAccelGroup *accel_group;

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title (GTK_WINDOW (window), "Menus");
    gtk_widget_set_size_request (window, 300,200);
    gtk_container_set_border_width (GTK_CONTAINER (window), 2);
    g_signal_connect(window, "delete-event", G_CALLBACK(gtk_main_quit), NULL);

    accel_group = gtk_accel_group_new ();
    gtk_window_add_accel_group (GTK_WINDOW (window), accel_group);

    box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
    gtk_container_add (GTK_CONTAINER (window), box);

    menubar = gtk_menu_bar_new ();
    gtk_box_pack_start (GTK_BOX (box), menubar, FALSE, TRUE, 0);

    menuitem = pv_get_menuitem_new_tree_of_file();
    gtk_menu_shell_append (GTK_MENU_SHELL (menubar), menuitem);

    menuitem = pv_get_menuitem_new_tree_of_help(window);
    gtk_menu_shell_append (GTK_MENU_SHELL (menubar), menuitem);

    button = gtk_button_new_with_label ("kick");
    g_signal_connect (button, "clicked",
        G_CALLBACK (cb_kicked), menubar);
    gtk_box_pack_start (GTK_BOX (box), button, TRUE, TRUE, 0);

return window;
}

int main(int argc, char **argv)
{
    GtkWidget *window;

    gtk_init(&argc, &argv);
    window = do_menus();
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
   
    gtk_widget_show_all (window);
    gtk_main();

    return 0;
}

==

以上です。

Gtk3でWindowに背景色を設定する

現在、Gtk3を使ってウィンドウアプリケーションを作成しています。
ところで、Gtk3ではWindow背景色の指定方法が面白いことになっていました。
(Gtk3を使う側からすれば、あまり笑ってもいられなかったですが。)




Gtk2までは、背景色の変更は以下の一行で済んでいました。
gtk_widget_modify_bg(window, GTK_STATE_NORMAL, &color);
(-> is deprecated)

上の関数は非推奨(deprecated=将来廃止)になっており、代わりに使うよう紹介されている新しい関数が、こちらです。
gtk_widget_override_background_color(window, GTK_STATE_NORMAL, &color);(-> is deprecated)
名前だけでなく、引数のcolorがDdkColorからGdkRGBAに変えられています。

面白いのはここからです。
ドキュメントを見ればわかるように、Gtk3ではこの関数も非推奨になっています。
代わりに『GtkStyleProviderを使って上手いことやれるようになりました!』とのこと。

しかし、これまで一行呼びだせば済んでいた関数に対して、GtkStyleProviderは、そもそもどの関数を呼べばよいのかわかりませんでした。
(多分setと名前に付いているやつだろう。しかし何を引数に渡せば背景色が変わるんだ?)
gtk3-demoを探しても、シンプルでコード量の短い背景色変更のサンプルが見つかりません。
仕方なくGtkのMailingListに質問を投げたところ、
Q:『もっと簡単な方法とか、CSSな方法のサンプルコードとかありませんか?』
A:『現在のGtk3で背景色を指定するにはCSSを使う以外の方法はない。あと過去ログ読め』
との返事が帰ってきました。
(こちらがそのやり取り)

実は、質問とは別に親切な方からメールが来て、その返答によれば「私が書いたサンプルがここにあるから参考にしてね」とのことでした。
(多分過去ログとの重複でMLが汚れるのに配慮しつつ、気を回してくださったのだと思います。感謝。)
そのURLはこちら:
http://www.gtkforums.com/viewtopic.php?f=3&t=988&p=72088=GTK3+with+CSS#p72088

つまり、Gtk3でWindow背景色を変更しようとしたとき、モダンな方法では一行のコードでは済まなくなったとのことでした。



ライバルであるQtがHTML的なUIツールキットに舵を切る流れを追って、CSSライクな記法を採用するのはまあわかりますし、同じことができる古い関数を保守したくないのも良くわかります。
しかし、これまで一行で簡単に出来ていたことが面倒になったのは事実で、少しばかり納得いかない気持ちになったりはしました。

サンプルコード

以下が、私が今のところ使っている、Gtk3のWindowで背景色を変更するコードです。
===
    GtkCssProvider *provider;
    provider = gtk_css_provider_new ();

    GdkDisplay *display;
    GdkScreen *screen;
    display = gdk_display_get_default ();
    screen = gdk_display_get_default_screen (display);
    gtk_style_context_add_provider_for_screen (screen,
        GTK_STYLE_PROVIDER (provider),
        GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);

    gtk_css_provider_load_from_data (GTK_CSS_PROVIDER(provider),
        " GtkWindow {\n"
        "   background-color: rgb (103, 103, 103);\n"
        "}\n", -1, NULL);
    g_object_unref (provider);
===

gtk3によるマウスカーソルの変更(落とし穴と解決策)

gtk3を導入してアプリケーションを書いています。
アプリケーション上で表示するマウスカーソルのアイコンを変更しようとして、落とし穴にはまったので、原因と解決策を紹介します。
(原因、というほど大げさなものではありませんが)

(画像にアイコンが無いのは単にgnome-screenshotの仕様)


現象としては、gdk_window_set_cursor()関数を呼んでも、マウスカーソルのアイコンが変更されず、機能しません。

出現するエラーメッセージは以下。
コマンドライン実行すると確認できます。
(PhotonVector.exe:19302): Gdk-CRITICAL **: gdk_window_set_cursor: assertion 'GDK_IS_WINDOW (window)' failed

で、原因ですが、
『gtk_widget_show() または gtk_widget_show_all()を呼ぶ前に、
マウスカーソル変更のgdk_window_set_cursor()を呼ぼうとすると、
引数に必要なGdkWindowを正常に取得できていないため、変更に失敗する』
ということのようです。

Sample Source

以下がサンプルソースです。
マウスカーソルのアイコン変更が機能することを確認できます。
落とし穴である不具合は、下のマウスカーソル指定をコメントアウトして、上のコメントアウトを解除すると再現を確認できるはずです。

任意の画像ファイル(透過png矢印画像)によるアイコン

環境はUbuntu15.10-amd64です。
なお、ライブラリ導入はこちら。
sudo apt-get install libgtk-3-dev -y
Makefileにしてしまったので、ビルドコマンドは記載できませんが、
pkg-config --libs --cflags gtk+-3.0
を絡めることになるかと。良いかんじに作ってください。

サンプルソースの動作が確認できれば、あとは任意のGdkCursorを生成することで、マウスカーソルを思い通りの画像に変更することができるはずです。

===
#include <gdk/gdk.h>
#include <gtk/gtk.h>

int main (int argc, char **argv){
    gtk_init(&argc, &argv);

    GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_widget_set_size_request (window, 300,200);

    g_signal_connect(window, "delete-event", G_CALLBACK(gtk_main_quit), NULL);

/*
    // ** error occurs (not work) use *set_cursor() before gtk_widget_show*()
    GdkWindow* win = gtk_widget_get_window(window);
    GdkCursor* cursor = gdk_cursor_new(GDK_PENCIL);
    gdk_window_set_cursor(win, cursor);
*/
    gtk_widget_show_all(window);

    GdkWindow* win = gtk_widget_get_window(window);
    GdkCursor* cursor = gdk_cursor_new(GDK_PENCIL);
    gdk_window_set_cursor(win, cursor);

    gtk_main();
return 0;
}

===

実際のアプリケーションでは以下のような感じで使っています。
====

 
GdkWindow *window = gtk_widget_get_parent_window(self->widget);
 
if(!window){
 
 
et_debug("GdkWindow not grub");
 
}else{
 
 
GdkCursor* cursor = gdk_cursor_new_from_pixbuf(
 
 
 
 
gdk_display_get_default(),
 
 
 
 
self->tools[tool_id]->icon_unfocus,
 
 
 
 
0,
 
 
 
 
0);
 
 
/*
 
 
GdkCursor* cursor = gdk_cursor_new_from_name(
 
 
 
 
gdk_display_get_default(),
 
 
 
 
"help");
 
 
*/
 
 
if(!cursor){
 
 
 
et_error("%d", tool_id);
 
 
}else{
 
 
 
gdk_window_set_cursor(window, cursor);
 
 
}
 
}

====

アイリスオーヤマ加湿器SHM-100Uレビュー (取り外し写真あり)


アイリスオーヤマ 加湿器 SHM-100U
写真は加湿器を裏側から撮ったものです。
以前から冬場は体調を崩しやすく、今年も冬が近づくにつれて、足の一部で肌荒れが出て痒くてたまりませんでした。薬を塗っても効果が薄かったため、今年は加湿器を購入することにしました。
加湿器は、効果が無かったり壊してしまったりしても良いよう、安価な加湿器の中から、 「アイリスオーヤマ 加湿器 加熱式 アロマ対応 グリーン SHM-100U
を選びました。
なお自室は、小さな鉄筋マンションの一室です。

箱を開けると加湿器は紙製の支持材に入っています。
取り出して確認すると、電源スイッチは裏側にあることがわかります。
表にあるLEDで光っているボタンは「リセットスイッチ」であるようです。

Web上のレビューによると「周辺が濡れる」という話があり、100均で適当な受け皿を買おうかとも思ったのですが、調度良いものが加湿器と一緒に入っていることに思い至りました。
(※火災の危険などあると思います。責任は持ちません。)

購入当初、裏側のリセットスイッチを使う頻度がわからなかったので、裏側の電源スイッチを表に置いて運用したら良いのではないかと思いました。今はまだそうしています。
しかし、一晩運用するなら翌朝には自動停止しており、電源スイッチではなくリセットが必要になるので、正面に置いて電源は付けっぱなしにするのが良さそうです。
推奨する使い方ではないのだと思いますが、動作させながらタンクを外して水を足すこともできます。私は慎重にやらないと周囲に水を撒いてしまいますが。

アイリスオーヤマ 加湿器 SHM-100U タンク
タンクは金属バネとゴム蓋による単純な仕組みです。台座の出っ張りからタンクの蓋を押し上げて開け、そこから重力で自然に給水する仕組みです。

アイリスオーヤマ 加湿器 SHM-100U 本体(蒸気の誘導ほか外した状態)

実際の使用時間ですが、私の環境では公称の8時間よりすこし短い時間で水タンクを使いきっているように思います。
しかし、とりあえず一晩使うには、寝る前にセットしておけば、朝起きた時にはタンクの水をを使いきって止まっているので、それはそれで便利です。
空気が乾いているかどうかは、私には体感ではわからないので、なんとも言えません。
しかし8時間弱動いていれば、9時間睡眠でもほとんどをカバーできるので、加湿器が常に動いていないと困る体質の方ということでなければ、この加湿器は良いと思います。

=======


複数画像の一括リサイズをコマンドラインで行う最小コマンド(ImageMagickのみ)

複数の写真画像をリサイズする作業はたまに発生します。
Linux環境だと特に、コマンド一発での処理を考えます。その度にGoogle検索でImageMagickの使い方を調べて、上位サイトの紹介するオプションを継ぎ接ぎしてい使っていたのですが。
毎回調べるのは面倒なので、覚えられるよう最低限の長さにまとめました。

本当に最低限は以下。
mogrify -resize 800x *.JPG
それなりに必要性のあるオプションを追加して、以下を覚えると良いです。
mogrify -strip -format jpg -quality 90 -resize 800x *.JPG




画像ファイル一覧

明度調整などのマトモな画像加工は諦めましょう。またはその都度調べましょう。
既にパイプを使いこなしているなら良いのですが、find,grepをパイプしてxargsで流しこむ、なんて流れを画像変換のためだけに覚えるのは面倒かと思います。
Gimpがコマンドラインからシェルスクリプトで使えたりすればよかったのですが、あちらは魔窟なので。
画像リサイズ程度の要望は、可能な限りImageMagickで処理。


以下解説。

mogrify
どう考えても同じImageMagickで"convert"のほうが覚えやすいですし、mogrifyでは通常は元ファイルが上書きされてしまいます。しかしconvertでは出力ファイル名が元ファイルを紐付けられない変なものになるので、すっきり諦めてmogrifyを覚えましょう。
なお、上書きには対象画像ファイルをまとたフォルダごとコピーして元画像をバックアップすることで対応します。下手に追加のコマンドを覚えるよりはるかに楽です。

-strip
EXIF情報を切り落とすオプション。ファイルサイズへの影響はともかく、当然付けておきます。

-format jpg
元ファイルが何JPGなのかわからないので標準的なjpg形式に。
format指定で拡張子が変更になる場合、元ファイルが保持されるという嬉しい副作用もあります。

-quarity 96
デフォルト92だという話ですが。デフォルト値があるならそれを使ったほうが良い派なのですが。これに関しては、写真によっては激しく劣化することがあったので。

-resize 800x
縦横比を保持するために横サイズのみ指定。お約束です。

*.JPG
対象ファイルを一括指定する記法。ワイルドカードを使ったありきたりな指定で覚えやすい。ImageMagickで完結するし。

以上です。

Ubuntu14.04ネットワークブート(pxe-pdhcp環境構築)

既にDHCPサーバがある環境で、DHCPとは独立したPXE Bootサーバを立てることができるpxe-pdhcpというソフトウェアがあります。今回はpxe-pdhcpで、Ubuntu14.04をディスクレスで起動させるまでの備忘録です。

PXE Bootによるネットワークブート環境を持っておくと、HDDを乗せないディスクレスクライアントを用意したり、ディスクドライブ無しでUbuntuなどのインストールができて便利です。



なお、ネットワークブート環境を構築したのは、10インチノートPCであるN10Jcを、Ubuntu14.04-32bitで動作確認するためです。
(N10Jcは、EeePC的ノートPCでありながら、nVidiaのGPUを搭載している珍しい機体です。
一方で搭載CPUが比較的初期のAtom N270であり、32bit版OSしか動作しないため、他PCを64bit環境で統一していると、現在では運用が少しめんどうな機体でもあります。)
こちらが、PXEでディスクレス起動するゲストPCになります。

PXEサーバとなるホストPCは、普通の64bitシステムです。(Ubuntu15.04-amd64)


こちらのブログのSadayuki Furuhashi氏が書いたpxe-pdhcpを使用します。
http://frsyuki.hatenablog.com/entry/20070926/p1

基本の手順は、11.04での設定・手順を紹介しているこちらから。
http://kimamahitori.blog.shinobi.jp/Entry/317/
UbuntuディスクISOのマウントなどはこちらを参考にしました。
http://blog.akagi.jp/archives/3391.html
なお、こちらで紹介されているのはサーバUbuntu版で、今回ネットワークブートさせたデスクトップ版とはディスク内の構成が違う模様です。
今回はデスクトップ版(LiveCD)の設定・起動方法を扱います。

ネットワークブートの設定手順

tftp

最初にpxeのディレクトリを作成します。
書き込み権限を付けておいたほうが、あとの作業が楽です。
mkdir /pxe
sudo chmod a+rw /pxe

ftpサーバを導入します。
sudo apt-get install -y inetutils-inetd tftpd-hpa -y


設定ファイルを編集します。
sudo vim /etc/default/tftpd-hpa

以下の内容に書き換え。
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/pxe"
#TFTP_ADDRESS="[::]:69"
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="--secure -vvv"
RUN_DAEMON="yes"


再起動して設定を反映します。
sudo service tftpd-hpa restart

nfs

nfsサーバを導入します。
sudo apt-get install nfs-kernel-server -y

設定ファイルを書き換えます。
sudo vim /etc/exports
以下の内容を追記(設定はひとつにつき一行に書いて折り返さない)。
/pxe/images/ubuntu-14.04-amd64  *(no_root_squash,ro,async,insecure)
/pxe/images/ubuntu-14.04-i386  *(no_root_squash,ro,async,insecure)


nfsサーバを再起動して設定を反映します
sudo service nfs-kernel-server restart

Ubuntu Disc Image

ftpでブートメニューが読まれた後に要求される、
起動したいUbuntu(Linux) のディスクイメージを、nfs上に展開します。
下記の空ディレクトリを作成します。
mkdir -p /pxe/images/ubuntu-14.04-amd64
mkdir -p /pxe/images/ubuntu-14.04-i386

ディレクトリにマウントします。
sudo mount -r -t iso9660 -o loop ~/ubuntu-14.04.1-desktop-i386.iso /pxe/images/ubuntu-14.04-i386/
sudo mount -r -t iso9660 -o loop ~/ubuntu-14.04-desktop-amd64.iso /pxe/images/ubuntu-14.04-amd64/

pxelinux.cfg/default (bootmenu)

クライアントPCがPXE機能で読み込む、ブートメニューや必要なバイナリを、ftp上に展開します。



ネットワークブート・メニュー表示に必要なバイナリを、以下から取得・配置します。
バイナリを取得
wget http://www.kernel.org/pub/linux/utils/boot/syslinux/syslinux-4.04.tar.gz
解凍し、中のファイルを以下のパスに配置します。
/pxe/menu.c32
/pxe/pxelinux.0

ネットワークブートのブートメニューを以下に設定ファイルとして配置します。
vim /pxe/pxelinux.cfg/default

設定ファイルの内容は以下 (IPアドレスは環境により適切に読み替えます。)
# /pxe/pxelinux.cfg/default
DEFAULT menu.c32
PROMPT 1
NOESCAPE 0
ALLOWOPTIONS 0
TIMEOUT 100

MENU TITLE PXE Boot Menu

LABEL Ubuntu 14.04 amd64
kernel images/ubuntu-14.04-amd64/casper/vmlinuz.efi
append boot=casper netboot=nfs nfsroot=192.168.1.21:/pxe/images/ubuntu-14.04-amd64 initrd=images/ubuntu-14.04-amd64/casper/initrd.lz -- splashd

LABEL Ubuntu 14.04 i386
kernel images/ubuntu-14.04-i386/casper/vmlinuz
append boot=casper netboot=nfs nfsroot=192.168.1.21:/pxe/images/ubuntu-14.04-i386 initrd=images/ubuntu-14.04-i386/casper/initrd.lz -- splashd

LABEL Ubuntu 14.04 amd64(non splash)
kernel images/ubuntu-14.04-amd64/casper/vmlinuz.efi
append boot=casper netboot=nfs nfsroot=192.168.1.21:/pxe/images/ubuntu-14.04-amd64 initrd=images/ubuntu-14.04-amd64/casper/initrd.lz


pxe-pdhcp

Sadayuki Furuhashi氏が作成したpxe-pdhcpをソースで取得し、ビルドして使用します。
今回はroot直下に/pxe-pdhcpディレクトリとしてソースとバイナリを展開しました。
sudo make  /pxe-pdhcp
chmod a+rw /pxe-pdhcp
cd /pxe-pdhcp
git clone https://github.com/frsyuki/pxe-pdhcp
なお、いくつかのブログでバッファオーバーフロー警告に対するコード修正が紹介されていますが、今回はしなくても動作したので、とりあえずコードを書き換えていません。

ビルドします。
make
実行します。(IPアドレスは環境により適切に読み替えます。)
sudo /pxe-pdhcp/pxe-pdhcp -l 0.0.0.0 -b 255.255.255.255 -t 192.168.1.21 -d pxelinux.0

クライアントPCからネットワークブートを実行

PXEサーバ(pxe-pdhcp)が起動している状態でクライアントPCからネットワークブート操作を行うことで、ネットワークブート可能です。



クライアントPCのネットワークブート方法は、マシンにより異なるので、サイト・マニュアル・グーグル検索を活用してください。
BIOS/EFIの設定画面で、PXEを有効にする必要があるかと思います。
とりあえず、一般的にはBIOS/EFI設定画面に行くなら、起動画面でDeleteキー押下かと思います。

また、ESC/F2キーなどでブートセレクタを呼び出せるPCもあります。
ブートセレクタにPXE(Network boot)が無い場合、BIOS/EFIの設定でPXEが無効になっているかもしれません。

ちなみに今回クライアントPCとして使用したN10Jcの場合は、
F2:BIOS設定
ESC:ブートメニュー
でした。

その他

手順中に、2箇所ほど、PXEサーバのIPアドレスを指定する個所があります。
(特にネットワークブートメニューのnfsroot設定)
そのため、PXEサーバマシンはIPアドレスを固定設定にすると、今後の利用が楽になります。

Ubuntuのディスク内容をNFS上に置く必要がありますが、毎度ISOファイルをマウントするのが面倒であれば、コピーして配置してしまうと楽になります。

ComfyUIでXYZ-PlotするワークフローのサンプルとTips(ComfyUI-XYZ-Stitch)

 ComfyUIでAutomatic1111的なXY-Plotをしたいのだが、ComfyUIデフォルト機能には用意されていない。     カスタムノードが必要となるのだが、Webには古いとか何故か動かないとかなXY-Plotパッケージの使ってみた系記事か、githubに上げられた...