試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

Vala言語で外部プロセスを実行する(スレッドを使用してGTK+のテキストビューに実行結果を表示・コード例)

Vala言語で外部プロセスを実行する(スレッドを使用してGTK+のテキストビューに実行結果を表示・メモ)」の内容を踏まえた上で、GTK+上のテキストビュー上に入力したコマンドを実行してテキストビューに結果を表示するためのコード例を貼り付ける。
今回、子プロセスのステータス値はPosix.waitpid()で取得し、子プロセスの監視(GLib.ChildWatch.add())は行っていない。
[任意]ファイル名: spawnasyncwithpipestest3.vala

using GLib;
using Gtk;
using Gdk;
using Posix;
using Pango;

/*
 * valac --thread --pkg posix --pkg gtk+-2.0 -o spawnasyncwithpipestest3 spawnasyncwithpipestest3.vala
 */

namespace SpawnAsyncWithPipesTest3
{
  class MainWindow : Gtk.Window
  {
    Gtk.AccelGroup accelgroup;
    Gtk.ImageMenuItem item_quit;
    Gtk.Menu menu_file;
    Gtk.MenuItem item_file;
    Gtk.MenuBar menubar;
    Gtk.Entry entry;
    Gtk.Button button;
    Gtk.TextView textview;
    Gtk.TextBuffer textbuf;
    Gtk.ScrolledWindow sw;
    Gtk.HBox hbox;
    Gtk.VBox vbox;
    GLib.Pid child_pid;
    int child_stdin;
    int child_stdout;
    int child_stderr;
    public MainWindow ()
    {
      /* ショートカットキー(アクセラレータ) */
      this.accelgroup = new Gtk.AccelGroup ();
      this.add_accel_group (this.accelgroup);
      /* メニュー項目 */
      this.item_quit = new Gtk.ImageMenuItem.from_stock (Gtk.STOCK_QUIT, accelgroup);
      this.menu_file = new Gtk.Menu ();
      this.menu_file.add (item_quit);
      this.item_file = new Gtk.MenuItem.with_mnemonic ("_File");
      this.item_file.set_submenu (menu_file);
      this.menubar = new Gtk.MenuBar ();
      this.menubar.append (item_file);
      /* 出力のテキストビューの下に1行テキスト入力欄と停止ボタンを横に並べる */
      this.entry = new Gtk.Entry ();
      this.button = new Gtk.Button.from_stock (Gtk.STOCK_STOP);
      this.button.sensitive = false;  // 最初は無効
      this.hbox = new Gtk.HBox (false, 0);
      this.hbox.pack_start (this.entry, true, true, 0);
      this.hbox.pack_start (this.button, false, false, 0);
      this.textview = new Gtk.TextView ();
      this.textbuf = this.textview.buffer;
      this.textview.editable = false;  // 編集不可
      this.textview.modify_font (Pango.FontDescription.from_string ("Monospace, Normal 10"));
      /* レイアウトなど */
      this.sw = new Gtk.ScrolledWindow (null, null);
      this.sw.add (this.textview);
      this.sw.set_policy (Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
      this.vbox = new Gtk.VBox (false, 0);
      this.vbox.pack_start (this.menubar, false, false, 0);
      this.vbox.pack_start (this.sw, true, true, 0);
      this.vbox.pack_start (this.hbox, false, false, 0);
      this.add (this.vbox);
      this.set_size_request (600, 360);
      /* テキスト入力欄をフォーカス */
      this.entry.grab_focus ();
      /* シグナル */
      this.entry.activate += this.on_entry_activated;
      this.button.clicked += (source) =>
      {
        /* Windows上ではプロセスIDから終了するには
         * http://support.microsoft.com/kb/409542/ja
         * のようにするが、Windows APIのバインディングはVala 0.7.4の時点ではない
         */
        Posix.kill ((Posix.pid_t) this.child_pid, Posix.SIGTERM);
        source.sensitive = false;
      };
      this.item_quit.activate += Gtk.main_quit;
      this.destroy += Gtk.main_quit;
    }
    void on_entry_activated (Gtk.Entry source)
    {
      weak GLib.Thread th_stdout, th_stderr;
      string[] argv;
      /* テキスト入力欄から文字列を取り出す */
      string cmdline = source.text;
      source.text = "";  // クリア
      try
      {
        /* コマンド行文字列をリストの形式に変換 */
        GLib.Shell.parse_argv (cmdline, out argv);
      }
      catch (GLib.ShellError e)
      {
        GLib.warning ("Cannot parse command line: %s\n(%s)", cmdline, e.message);
        return;
      }
      GLib.debug ("cmdline: %s", cmdline);
      try
      {
        if (GLib.Process.spawn_async_with_pipes (null,
                                                 argv,
                                                 null,
                                                 GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD,
                                                 null,
                                                 out this.child_pid,
                                                 out this.child_stdin,
                                                 out this.child_stdout,
                                                 out this.child_stderr))
          this.button.sensitive = true;
        try
        {
          th_stdout = GLib.Thread.create (this.display_stdout, false);
          th_stderr = GLib.Thread.create (this.display_stderr, false);
        }
        catch (GLib.ThreadError e)
        {
          GLib.warning ("ThreadError: %s", e.message);
        }
      }
      catch (GLib.SpawnError e)  // 子プロセス起動に失敗
      {
        Gtk.TextIter iter;
        GLib.warning ("spawn failed: %s", e.message);
        this.textbuf.get_end_iter (out iter);
        this.textbuf.place_cursor (iter);
        this.textbuf.insert_at_cursor ("Failed to execute \"%s\"\n(%s)\n".printf (cmdline, e.message), -1);
        this.textview.scroll_to_mark (this.textbuf.get_insert (), 0, false, 0, 0);
      }
    }
    void *display_stdout ()
    {
      int status;
      Gtk.TextIter iter;
      string statusmsg;
      this.display_output (this.child_stdout);
      Posix.waitpid ((Posix.pid_t) this.child_pid, out status, 0);
      GLib.Process.close_pid (this.child_pid);

      statusmsg = "child exited, raw status: %d".printf (status);
      if (GLib.Process.if_exited (status))
        statusmsg += " exit status: %d\n".printf (GLib.Process.exit_status (status));
      Gdk.threads_enter ();
      this.textbuf.get_end_iter (out iter);
      this.textbuf.place_cursor (iter);
      this.textbuf.insert_at_cursor (statusmsg, -1);
      this.textview.scroll_to_mark (this.textbuf.get_insert (), 0, false, 0, 0);
      this.button.sensitive = false;
      Gdk.threads_leave ();
      /*
       * 条件によって(?)コンパイルエラーが出るので記述
       * "missing return statement at end of method body"
       */
      return null;
    }
    void *display_stderr ()
    {
      this.display_output (this.child_stderr);
      return null;
    }
    void display_output (int fd)
    {
      string line;                    // 出力の1行を格納
      string encoding;
      GLib.IOStatus iostatus;         // 出力の読み込みで使用
      size_t length, terminator_pos;  // IOチャンネルから書き込まれる
      Gtk.TextIter iter;
      /* ファイル記述子を開くのにIOチャンネルを使用する */
      GLib.IOChannel ioch = new GLib.IOChannel.unix_new (fd);
      /* UTF-8以外の場合にエンコーディングを指定 */
      if (GLib.get_charset (out encoding) == false)
      {
        try
        {
          ioch.set_encoding (encoding);
        }
        catch (GLib.IOChannelError e)
        {
          ;
        }
      }
      for (;;)
      {
        try
        {
          /* 出力から1行読み込む */
          iostatus = ioch.read_line (out line, out length, out terminator_pos);
        }
        catch (GLib.IOChannelError e)
        {
          break;
        }
        catch (GLib.ConvertError e)
        {
          break;
        }
        if (iostatus != GLib.IOStatus.NORMAL)
          break;
        Gdk.threads_enter ();
        this.textbuf.get_end_iter (out iter);
        this.textbuf.place_cursor (iter);
        this.textbuf.insert_at_cursor (line, -1);
        this.textview.scroll_to_mark (this.textbuf.get_insert (), 0, false, 0, 0);
        Gdk.threads_leave ();
      }
      /* 後始末 */
      try
      {
        ioch.shutdown (false);
      }
      catch (GLib.IOChannelError e)
      {
        ;
      }
    }
  }
  class MainClass
  {
    public static int main (string[] args)
    {
      if (! GLib.Thread.supported ())
      {
        GLib.error ("This program needs threading support.\n");
        return 1;
      }
      Gtk.init (ref args);
      Gdk.threads_init ();
      MainWindow win = new MainWindow ();
      win.show_all ();
      Gtk.main ();
      return 0;
    }
  }
}

関連記事:

使用したバージョン:

  • Vala 0.7.4, 0.7.5